Sending a “Qualified” grade from your CRM to an ad platform is a data contract, not a checkbox. Before anyone builds the feed, write down which business event counts, which record it belongs to, when it happened, how a retry is recognized, and what happens when the grade is later changed. Then trace one record end to end before you automate the rest.
This guide is the platform-neutral planning layer. It covers the event dictionary, identifiers, time fields, delivery states and a diagnostic path. The platform-specific setup lives in two companion guides: Facebook and Instagram ads and Google Ads.
Five things that get confused
When someone says “the Qualified event is working”, they could mean any of five different things. Each has its own evidence.
| Layer | Question it answers | Where the evidence lives |
|---|---|---|
| Scope | Did our own integration policy include this record for this platform? | Your written scope decision and the held states in Step 4 |
| Receipt | Did the platform accept what we sent? | The delivery log and the platform’s response to each send |
| Match | Did the platform connect it to a click, lead or person it knows? | Platform diagnostics, where the platform exposes them |
| Attribution | Did the platform credit it to a campaign, under its own rules? | Campaign reports |
| Optimization | Is a campaign actually using it as its goal? | Campaign settings, plus whatever eligibility rules the platform documents |
Scope is your decision; the other four are evidence. A record you kept out of scope was never tested for receipt, match or attribution, so it says nothing about what the platform could have done with it. A send can be accepted and never matched. A matched event can fall outside an attribution window. An attributed event may not be the goal any campaign optimizes toward. Report each layer separately, and never treat “accepted” as proof that results improved.
Step 1: write the event dictionary
Start with business events, in your own words. Platform event names come later, when you map them.
| Business event | Trigger | Who or what records it | Evidence kept | Send for ad feedback? |
|---|---|---|---|---|
inquiry_accepted | The form system accepts a valid submission | Form system | submission_id, acceptance time, landing page | Often already sent from the website. Decide once, per platform |
candidate_graded | A rep applies the written rules and records a grade | Development rep | Grade, reasons, rule version, reviewer, time | This guide’s chosen optimization-event policy sends only Qualified, as its own event. That’s a choice, not a rule of every CRM integration: some product integrations ask for more stages (Meta’s CRM integration, for one, has you configure a sales funnel) |
conversation_held | A development call actually takes place | Development rep | Meeting outcome and time. A booking is not a held call | Optional secondary event |
agreement_signed | The signed franchise agreement is confirmed | Development director | Agreement record and signed date | Report in the CRM. Usually too late and too rare to steer campaigns |
grade_corrected | A grade is changed after it was recorded | Reviewer | Old grade, new grade, reason, time | Triggers the correction rules in Step 4 |
agreement_reversed | A signed agreement is cancelled or rescinded | Development director | Reversal date and reason | Report in the CRM. Cost per signed franchise is net of reversals |
Grades follow the shared rules: Qualified (green), Nurture (amber), Disqualified (red). “Not yet graded” is a status, not a grade, so it never fires candidate_graded. The grading rules themselves are in How to qualify franchise leads. If two reps would grade the same record differently, fix that first. A feed only copies the inconsistency faster.
Step 2: keep event identifiers and person identifiers apart
Most feed bugs come from mixing up what an ID points to.
| Identifier | Points to | Example (fictional) | Rule |
|---|---|---|---|
crm_contact_id | A person | C2207 | One per person. A resubmission doesn’t create a new one |
submission_id | One inquiry | S0417 | Created when the form accepts the submission |
event_instance_id | One business event | EV-Q-0417-001 | Assigned once when the event is recorded, stored, never rebuilt or edited. A retry reuses it. rule_version (v2.1) is a separate field, not part of the ID |
delivery_id | One attempt to send | D9031 | New on every attempt, including retries |
| Platform click or lead IDs | A click, or a platform-hosted lead | gclid, Meta Lead ID | Captured at arrival, never typed in, never invented |
| Hashed contact details | A person, as the platform may know them | Hashed email | Sent only where consent and the platform’s rules allow |
One person can produce several legitimate events. An inquiry from candidate C2207 and that candidate’s later Qualified grade are two events with two event_instance_id values. They are not duplicates because they share a person. A duplicate is the same business event sent twice.
Don’t build the ID from parts like S0417:qualified:v2.1. A rule-version change would mint a “new” ID for the same historical outcome, and a genuine later transition under the same version would collide with the first. Persist the ID instead, and write down a count policy. This guide’s example policy: one Qualified conversion per candidate per platform. A regrade of the same outcome under a new rule version is a correction record, not a new conversion. A genuine later transition (Qualified, then Nurture on new evidence, then Qualified again) gets a new instance ID in your log, but under this policy it is not sent as a second Qualified conversion. If you choose a different policy, state it the same way.
Step 3: record three times, in one time zone convention
| Field | Meaning | Example (fictional) |
|---|---|---|
occurred_at | When the business outcome happened | 2026-10-08T15:20:00-05:00 (Austin, CDT) |
recorded_at | When it was written into the CRM | 2026-10-08T15:24:10-05:00 |
sent_at | When each delivery attempt left your system | 2026-10-09T02:00:05Z |
Store the offset or UTC with every value. Send occurred_at as the event time, not the time of the upload. If a nightly batch fails and runs two days later, the grade still happened on the 8th. Platforms set their own limits on how old an event can be. Check those in the platform’s current documentation before you design a weekly batch.
Step 4: define delivery and correction states
Every eligible event sits in exactly one state at a time.
| State | Meaning | Next action |
|---|---|---|
held_out_of_scope | Real event, outside the integration scope you chose for this platform | None for this platform. Report it in the CRM |
held_no_consent | Consent doesn’t permit sending this person’s data | None. Count it, don’t send it |
held_missing_identifier | No click ID, lead ID or permitted contact data to match on | Fix capture at the form. Don’t backfill guesses |
queued | Ready for the next send | Send |
sent | At least one delivery attempt made | Wait for the response |
accepted | The platform’s response said it was received | Done for receipt. Match and attribution are checked separately |
rejected | The platform’s response gave an error | Fix the cause, resend with the same event_instance_id |
superseded | The grade was corrected after sending | Write a separate correction record. Send a retraction only where the platform supports one, then mark which happened |
Retries reuse the event_instance_id and get a new delivery_id. A correction never edits history. The original event and the correction record both stay in the log.
held_out_of_scope is a policy you choose per platform, not a universal rule against sending non-ad records. Google’s enhanced-conversions troubleshooting documentation, checked 28 October 2026, explains that uploads include organic and other-channel conversions and only a portion are attributed to Google Ads, and a candidate your CRM records as broker-introduced may have clicked an ad first. A narrow scope (say, only records carrying that platform’s click or lead ID) is easier to audit. A wider scope gives the platform more to match. Write the choice down for each platform, and don’t assume every platform needs the same filter.
On corrections, regrade only on evidence. A Qualified candidate whose funding fell through becomes Nurture only when the rep confirms a new funding source and date inside the permitted band and no other rule fails. A confirmed must-have failure is Disqualified. If the new information leaves a required answer unresolved, the current assessment becomes Not yet graded while the previous grade stays in history. Either way, record the reassessment separately; funding trouble is not automatically Nurture.
A complete sample record
Below is one fictional Northfield Inspections candidate, written in an internal schema. This is your own log format, not any platform’s payload. Each platform needs its own field mapping, built from its current documentation.
{
"example_flag": "FICTIONAL EXAMPLE",
"event_instance_id": "EV-Q-0417-001",
"event_type": "candidate_graded",
"grade": "Qualified",
"grading_status": "Graded",
"rule_version": "v2.1",
"reviewer": "Sam (development rep)",
"occurred_at": "2026-10-08T15:20:00-05:00",
"recorded_at": "2026-10-08T15:24:10-05:00",
"person": { "crm_contact_id": "C2207", "email": "jordan.example@example.com", "phone": "+1-512-555-0142" },
"inquiry": {
"submission_id": "S0417",
"submitted_at": "2026-10-06T19:02:00-05:00",
"landing_page": "/franchise/texas/",
"territory_id": "TX-AUS-02",
"source": "google / cpc / category_ownership",
"click_ids": { "gclid": "EXAMPLE-GCLID-0417", "gbraid": null, "wbraid": null, "fbclid": null, "meta_lead_id": null }
},
"consent": { "ad_measurement": "granted", "captured_at": "2026-10-06T19:01:40-05:00" },
"feedback": {
"platform": "google_ads",
"state": "accepted",
"deliveries": [
{ "delivery_id": "D9031", "sent_at": "2026-10-09T02:00:05Z", "response": "error: invalid time format" },
{ "delivery_id": "D9044", "sent_at": "2026-10-10T02:00:04Z", "response": "accepted" }
]
},
"corrections": []
}
Read it top to bottom and you can answer every question a reviewer will ask. What happened (Qualified under v2.1), to whom (C2207, inquiry S0417), when (8 Oct, 15:20 Austin time), from which click (one gclid), with what permission (consent granted before submission), and how delivery went (one rejection for a time format bug, then accepted on the retry with the same instance ID, EV-Q-0417-001). The rule version sits in its own field, so a later rule change can’t alter the ID. The contact details are fictional and use reserved example values.
A month of feedback, counted honestly
Fictional Northfield month, October 2026, Google Ads. Northfield’s chosen scope for this platform: every Qualified candidate who submitted a web inquiry, whatever source the CRM records, if consent and a usable identifier allow. Phone-only intake is out of scope for now.
| Line | Count | How it’s derived |
|---|---|---|
| Qualified grades recorded | 14 | CRM, candidate_graded with grade Qualified |
| Out of scope, chosen policy | 1 | Referral recorded by phone, no web inquiry. CRM reporting only |
| In scope | 13 | 14 − 1. Includes 2 broker-introduced candidates who also submitted the web form |
| Held, no consent | 1 | |
| Held, missing identifier | 1 | Click ID lost on a redirect, no permitted contact data |
| Sent | 11 | 13 − 1 − 1 |
| Delivery attempts | 12 | 11 events + 1 retry |
| Rejected on first attempt | 1 | Time format error, fixed |
| Accepted | 11 | 10 first time + 1 on retry |
| Superseded after sending | 1 | Rep confirmed a new funding source and date inside the permitted band, no other rule failed; regraded Nurture on 27 Oct, correction record kept |
| Credited in campaign reports | 6 | Platform report, under its own attribution settings |
The gap between 11 accepted and 6 credited is a match and attribution question, not a delivery failure. Your log can’t settle it on its own, including whether the 2 broker-introduced candidates had an earlier ad interaction. The gap between 14 Qualified and 11 sent is fully explained by your own scope and states, and that’s the part you control.
The superseded record still counts as accepted for receipt. For business reporting it’s now Nurture. Report “11 accepted, 1 since superseded”, so the correction stays visible.
Diagnostic path: when the CRM and the platform disagree
Work down the layers in order and stop at the first one that fails.
- Did the business event exist? Check the CRM for the grade, the rule version and
occurred_at. If the grade was set by a bulk edit, the event may not have fired at all. - Was it in scope, and sendable? Check your scope decision and the held states. Missing identifiers usually point to capture: the click ID isn’t read on arrival, a redirect strips it, or it isn’t copied into the form’s hidden fields.
- Was it sent? Look for a
delivery_id. No delivery means a trigger or schedule problem. - Was it accepted? Read the platform’s response for that delivery. Errors are usually format, time or identifier problems.
- Was it matched? Use the platform’s own diagnostics, where they exist. Low match rates point to identifier quality, not volume.
- Was it attributed? Compare campaign reports as an aggregate, and note the attribution settings. Don’t expect record-level agreement.
- Is it the optimization goal? Check what each campaign is actually set to optimize for. A working feed that no campaign uses changes nothing.
The same layers apply to reconciling lead counts. The record-matching method is in Ad platform versus CRM counts, and capture of click IDs at arrival is in Source tracking.
Choosing what to send
A later stage is closer to a signed franchise but rarer and slower. An earlier stage arrives faster and more often but says less about fit. A Qualified grade is often the practical middle, provided it’s assigned quickly and consistently. Sending only Qualified is still an optimization-event policy you choose, not something every CRM integration accepts as its only input. Whether a given event can be used as a campaign goal depends on the platform’s current rules and your account, which the Meta and Google guides cover.
Keep signed agreements in business reporting either way. Cost per signed franchise is still the number that decides budget, and it’s calculated in your CRM, not by the platform: see Cost per signed franchise.
When the feed goes live, judge campaign results over a defined comparison period that’s long enough for grades to mature, and log anything else that changed at the same time. A new feed and a new ad set launched in the same week can’t be told apart afterwards.
Start here
Copy the event dictionary and agree it with whoever grades candidates. Then pick one recent Qualified candidate and fill in the sample record by hand, from real systems. Every field you can’t fill is a gap to fix before anyone automates the feed.
Downloads: O16-event-dictionary.csv (events, identifiers, time fields and states), O16-sample-business-record.json (the record above) and O16-feedback-monitor.csv (the monthly count, with formulas shown).
Related: Qualifying candidates · Facebook ads for franchise recruitment · Google Ads for franchise recruitment
Put it to work on your recruitment system
Bring your recruitment page and anonymized stage counts to a diagnostic. We’ll identify what to test and how to measure it.
Discuss your franchisor recruitment system