Zernio
Zernio
OverviewWebhooksPost webhooksInbox webhooksAutomation webhooksSupport run webhooksAccount webhooksAnalytics webhooksAds webhooksCall webhooksWhatsApp webhooksPhone number webhooksSMS registration webhooksBranded Calling webhooks
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Webhooks

Ads webhooks

Receive an event when an ads account finishes its first sync, when an ad account stops or resumes syncing, when a Meta Lead Gen form gets a lead, and when an ad object changes status.


Ads events cover the initial backfill after connecting an ads-capable account, ad accounts that stop and resume syncing, real-time leads from Meta Lead Gen forms, and ad object status changes. Subscribe with POST /v1/webhooks/settings and the event names below (first event); delivery, retries and signatures are the same for every event (how webhooks behave).

Events

EventDescription
account.ads.initial_sync_completedThe initial 90-day backfill completed for an ads-enabled account. Once per account.
account.ads.sync_failedAn ad account's ads stopped syncing. Once per episode, until it recovers.
account.ads.sync_recoveredAn ad account reported by account.ads.sync_failed is syncing again.
lead.receivedA new lead was submitted against a Meta Lead Gen form.
ad.status_changedAn ad, ad set or campaign changed status on the ad platform. Meta only.

How it behaves

The initial sync reports success or failure once

Zernio runs the initial sync after an ads-capable account is connected: ad-account discovery plus a 90-day historical ad backfill. sync reports whether the backfill succeeded fully or partially and how many ads were synced versus failed. When scoping was applied at connect time (scoping sync to specific ad accounts), account.platformAdAccountId echoes the chosen ad account when the scope is exactly one, and account.platformAdAccountIds lists every act_* synced.

On failure (sync.status is "failure") the payload adds fields so you can branch without parsing prose: sync.error (the raw platform error, truncated to about 2 KB), sync.errorCode and sync.errorSubcode (platform-native codes when parseable, for example Meta 190 or 10), and sync.errorCategory, a stable enum of token_invalid, permission_denied, no_ad_accounts, rate_limited, discovery_failed or unknown. New values may be added; existing ones are stable.

A failed sync is not final. Running the ads connect flow again (Connect ads) re-queues the 90-day backfill and the event fires again with the new outcome; Zernio skips the re-queue only when a backfill has already completed with at least one ad, or when one started less than 20 minutes ago. That is the fix for token_invalid, permission_denied and rate_limited. no_ad_accounts means discovery found no ad account on the grant, so reconnecting changes nothing until the user has one.

A stalled ad account is reported once, and again when it recovers

After the initial sync, Zernio keeps each ad account's ads and metrics fresh on a schedule. An hourly check looks at every ad account that has live ads on a connected account, on any ads platform, and treats it as failing when no sync succeeded for 24 hours, or when every live ad in it has reached the retry cap. account.ads.sync_failed then fires once for that ad account, and nothing more until account.ads.sync_recovered fires once when a sync succeeds again. Metrics for the ad account are stale in between, so this is the signal to flag the numbers in your UI rather than to poll.

adAccount.platformAdAccountId names the ad account (act_* on Meta) and account.accountId the connection it belongs to, so one connection with several ad accounts can report each separately. sync.errorCategory is the field to branch on:

  • ad_account_not_listed: the platform no longer returns the ad account to this connection. Access was removed, or the platform changed something on its side; check the ad account's permissions for the connected user, then reconnect.
  • sync_error: the platform returned an error on the sync. sync.error carries the detail, for display and debugging.
  • stale: no sync succeeded and no error was recorded. Zernio's own retries usually clear it.

New values may be added. sync.lastSuccessfulSyncAt is the last good sync and sync.failureCount the consecutive failed attempts. On account.ads.sync_recovered, sync.failingSince is the last successful sync before the failure started.

A disconnected connection gets account.disconnected instead: its ad accounts are not reported here, and no recovery event follows for them. An ad account that never synced successfully, or whose last good sync is more than 3 days old, is not announced either.

Leads arrive in real time from Meta's Page webhook

Zernio ingests leads through the Page leadgen webhook and forwards each one as lead.received. lead.fields is the flattened question-key to answer map; for multiple-choice questions the value is the option key, for example k1, not the display label. lead.formId, lead.adId and lead.campaignId give provenance, and lead.adId is null for organic or test leads. Deduplicate on lead.leadgenId (Meta's lead id) or the event id. account.profileId names the Zernio profile of the Facebook account that received the lead, so a multi-tenant integration routes leads the same way as every other event; the key is always present and is null only when the lead has no profile on record.

Leads need ads enabled on the team, and the gate is a permission rather than a check at delivery time: Zernio asks for leads_retrieval on the Facebook consent screen only when the team has ads enabled, and Meta refuses the Page's leadgen subscription without it, so no lead ever arrives. A Facebook account connected before ads was enabled needs a reconnect to pick the permission up.

A Page can also lose its leadgen subscription while every other event keeps arriving, so nothing looks wrong. Read the Page's webhook subscription to check: leadgen: false (with a warning) means leads are not reaching Zernio, and Re-subscribe a Page to webhooks re-sends the full field set and returns what Meta actually granted.

Status changes come from two Meta fields

Zernio sources ad.status_changed from two Meta ad_account webhook fields. in_process_ad_objects means the object finished processing and left IN_PROCESS; status.raw carries Meta's status_name (ACTIVE, PAUSED, PENDING_REVIEW, ARCHIVED, DELETED, DISAPPROVED). with_issues_ad_objects means the object entered WITH_ISSUES; status.raw is WITH_ISSUES and error is filled from Meta's error_code, error_summary and error_message.

adObject.level is CAMPAIGN, AD_SET or AD; creative-level events are not forwarded. Branch on status.raw, and use error.code as the stable discriminator: error.summary and error.message are localized to the ad-account owner's Meta locale. error is present on most WITH_ISSUES events, can be absent because Meta does not always include diagnostics, and never appears on any other status, so null-check it before reading error.code.

Matching is keyed on adObject.platformAdAccountId. When several connected metaads accounts point at the same Meta ad account, each receives its own delivery.

Subscribing to ad.status_changed needs ads access on the team: without it POST /v1/webhooks/settings answers 403 with code ads_addon_required. Usage-based billing includes ads on every account (pricing).


account.ads.initial_sync_completed

The initial sync completed for an ads-enabled account: ad-account discovery plus the 90-day backfill. sync carries the outcome and, on failure, the error fields described above.


Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Webhook payload for account.ads.initial_sync_completed events. Fired once per ads-enabled account when the initial discovery + 90-day ad backfill finishes (whether it succeeded fully, partially, or failed).

Response Body

Example Requests

Webhook payload for account.ads.initial_sync_completed events. Fired once per ads-enabled account when the initial discovery + 90-day ad backfill finishes (whether it succeeded fully, partially, or failed).

POST/account.ads.initial_sync_completed


account.ads.sync_failed

An ad account's ads stopped syncing: no successful sync for 24 hours, or every live ad at the retry cap. Fires once until the ad account recovers.


Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Webhook payload for account.ads.sync_failed events. Fired once per ad account when its ads stop syncing: no successful sync for 24 hours, or every live ad in it at the retry cap. It does not fire again for the same ad account until account.ads.sync_recovered. Metrics for the ad account are stale meanwhile.

Response Body

Example Requests

Webhook payload for account.ads.sync_failed events. Fired once per ad account when its ads stop syncing: no successful sync for 24 hours, or every live ad in it at the retry cap. It does not fire again for the same ad account until account.ads.sync_recovered. Metrics for the ad account are stale meanwhile.

POST/account.ads.sync_failed


account.ads.sync_recovered

An ad account previously reported by account.ads.sync_failed synced successfully again.


Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Webhook payload for account.ads.sync_recovered events. Fired once when an ad account previously reported by account.ads.sync_failed syncs successfully again.

Response Body

Example Requests

Webhook payload for account.ads.sync_recovered events. Fired once when an ad account previously reported by account.ads.sync_failed syncs successfully again.

POST/account.ads.sync_recovered


lead.received

A new lead was submitted against a Meta Lead Gen (Instant) form. lead.fields holds the answers keyed by question.


Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Webhook payload for lead.received events (Meta Lead Gen / Instant Forms).

Response Body

Example Requests

Webhook payload for lead.received events (Meta Lead Gen / Instant Forms).

POST/lead.received


ad.status_changed

A campaign, ad set or ad on a connected Meta ad account (metaads) changed status. status.raw is Meta's status name and error is present when the object entered WITH_ISSUES.


Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Webhook payload for the ad.status_changed event. Currently emitted only for Meta (metaads).

Sourced from two Meta ad_account webhook fields:

  • in_process_ad_objects - the ad object finished processing and exited IN_PROCESS. status.raw carries Meta's status_name.
  • with_issues_ad_objects - the ad object entered WITH_ISSUES. status.raw is WITH_ISSUES and the error block is populated from Meta's error_code / error_summary / error_message.

Review outcomes (an ad leaving PENDING_REVIEW for ACTIVE, DISAPPROVED and so on) are also emitted from Zernio's own ad sync, so they arrive even when Meta skips the webhook. status.raw is Meta's effective_status. An ad-level outcome is delivered once per status: whichever source sees it first sends it.

Response Body

Example Requests

POST/ad.status_changed

Related

  • Webhooks: create an endpoint, retries, signatures.
  • Meta Ads: campaigns, boosting and lead forms.
  • Connecting accounts: scope the sync to specific ad accounts.
  • List leads: the same leads on demand.
Was this page helpful?

Analytics webhooks

Receive a cursor each time an account's analytics sync completes, then read every changed post in one call to the delta feed.

Call webhooks

Receive an event when a call rings, ends or fails on a phone or WhatsApp number, and when a WhatsApp user answers a call-permission request.

On this page

EventsHow it behavesThe initial sync reports success or failure onceA stalled ad account is reported once, and again when it recoversLeads arrive in real time from Meta's Page webhookStatus changes come from two Meta fieldsaccount.ads.initial_sync_completedaccount.ads.sync_failedaccount.ads.sync_recoveredlead.receivedad.status_changedRelated
id*string

Stable webhook event ID: the dedupe key, also sent as the X-Zernio-Event-Id header and identical on every retry and redelivery. It identifies the event only, never an account or other resource.

event*"account.ads.initial_sync_completed"

Value in

  • "account.ads.initial_sync_completed"
account*
sync*

Summary of the initial ads sync backfill results.

timestamp*string

UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.

Formatdate-time
id*string

Stable webhook event ID: the dedupe key, also sent as the X-Zernio-Event-Id header and identical on every retry and redelivery.

event*"account.ads.sync_failed"

Value in

  • "account.ads.sync_failed"
account*
adAccount*
sync*
timestamp*string

UTC time at which Zernio generated this event. Retries and redeliveries keep the original value.

Formatdate-time
id*string

Stable webhook event ID: the dedupe key, also sent as the X-Zernio-Event-Id header and identical on every retry and redelivery.

event*"account.ads.sync_recovered"

Value in

  • "account.ads.sync_recovered"
account*
adAccount*
sync*
timestamp*string

UTC time at which Zernio generated this event. Retries and redeliveries keep the original value.

Formatdate-time
id*string

Stable webhook event ID: the dedupe key, also sent as the X-Zernio-Event-Id header and identical on every retry and redelivery. It identifies the event only, never an account or other resource.

event*"lead.received"

Value in

  • "lead.received"
lead*
account*
timestamp*string

UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.

Formatdate-time
id*string

Stable webhook event ID: the dedupe key, also sent as the X-Zernio-Event-Id header and identical on every retry and redelivery. It identifies the event only, never an account or other resource.

event*"ad.status_changed"

Value in

  • "ad.status_changed"
account*

The connected ad-platform account that owns the ad object.

adObject*

The ad-platform object the status change applies to.

status*

Status info. Branch on status.raw to handle each transition.

error?

Optional. Present on most WITH_ISSUES events, carrying the platform's error diagnostics. May be absent on some WITH_ISSUES events (Meta does not always include diagnostics). Always absent for any other status.raw value. Always null-check before reading.

timestamp*string

UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.

Formatdate-time