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.

LayerQuestion it answersWhere the evidence lives
ScopeDid our own integration policy include this record for this platform?Your written scope decision and the held states in Step 4
ReceiptDid the platform accept what we sent?The delivery log and the platform’s response to each send
MatchDid the platform connect it to a click, lead or person it knows?Platform diagnostics, where the platform exposes them
AttributionDid the platform credit it to a campaign, under its own rules?Campaign reports
OptimizationIs 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 eventTriggerWho or what records itEvidence keptSend for ad feedback?
inquiry_acceptedThe form system accepts a valid submissionForm systemsubmission_id, acceptance time, landing pageOften already sent from the website. Decide once, per platform
candidate_gradedA rep applies the written rules and records a gradeDevelopment repGrade, reasons, rule version, reviewer, timeThis 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_heldA development call actually takes placeDevelopment repMeeting outcome and time. A booking is not a held callOptional secondary event
agreement_signedThe signed franchise agreement is confirmedDevelopment directorAgreement record and signed dateReport in the CRM. Usually too late and too rare to steer campaigns
grade_correctedA grade is changed after it was recordedReviewerOld grade, new grade, reason, timeTriggers the correction rules in Step 4
agreement_reversedA signed agreement is cancelled or rescindedDevelopment directorReversal date and reasonReport 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.

IdentifierPoints toExample (fictional)Rule
crm_contact_idA personC2207One per person. A resubmission doesn’t create a new one
submission_idOne inquiryS0417Created when the form accepts the submission
event_instance_idOne business eventEV-Q-0417-001Assigned 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_idOne attempt to sendD9031New on every attempt, including retries
Platform click or lead IDsA click, or a platform-hosted leadgclid, Meta Lead IDCaptured at arrival, never typed in, never invented
Hashed contact detailsA person, as the platform may know themHashed emailSent 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

FieldMeaningExample (fictional)
occurred_atWhen the business outcome happened2026-10-08T15:20:00-05:00 (Austin, CDT)
recorded_atWhen it was written into the CRM2026-10-08T15:24:10-05:00
sent_atWhen each delivery attempt left your system2026-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.

StateMeaningNext action
held_out_of_scopeReal event, outside the integration scope you chose for this platformNone for this platform. Report it in the CRM
held_no_consentConsent doesn’t permit sending this person’s dataNone. Count it, don’t send it
held_missing_identifierNo click ID, lead ID or permitted contact data to match onFix capture at the form. Don’t backfill guesses
queuedReady for the next sendSend
sentAt least one delivery attempt madeWait for the response
acceptedThe platform’s response said it was receivedDone for receipt. Match and attribution are checked separately
rejectedThe platform’s response gave an errorFix the cause, resend with the same event_instance_id
supersededThe grade was corrected after sendingWrite 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.

LineCountHow it’s derived
Qualified grades recorded14CRM, candidate_graded with grade Qualified
Out of scope, chosen policy1Referral recorded by phone, no web inquiry. CRM reporting only
In scope1314 − 1. Includes 2 broker-introduced candidates who also submitted the web form
Held, no consent1
Held, missing identifier1Click ID lost on a redirect, no permitted contact data
Sent1113 − 1 − 1
Delivery attempts1211 events + 1 retry
Rejected on first attempt1Time format error, fixed
Accepted1110 first time + 1 on retry
Superseded after sending1Rep 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 reports6Platform 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.

  1. 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.
  2. 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.
  3. Was it sent? Look for a delivery_id. No delivery means a trigger or schedule problem.
  4. Was it accepted? Read the platform’s response for that delivery. Errors are usually format, time or identifier problems.
  5. Was it matched? Use the platform’s own diagnostics, where they exist. Low match rates point to identifier quality, not volume.
  6. Was it attributed? Compare campaign reports as an aggregate, and note the attribution settings. Don’t expect record-level agreement.
  7. 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

More franchisor guides · Read the research and limitations