Skip to main content
When someone taps a Click-to-WhatsApp (CTWA) ad or an organic post and writes to your official-API number, Meta attaches the origin of that click to the first inbound message. Pilot Status stores it, and these two endpoints read it back: one message by message, one aggregated per ad.

Endpoints

GET https://pilotstatus.com.br/v1/referrals GET https://pilotstatus.com.br/v1/referrals/summary
Both require a number-scoped API key (ps_*) in the x-api-key header. Tenant-scoped keys return 403.
In practice only official API (Meta) numbers carry an ad origin — the referral block is a WhatsApp Cloud API webhook field, so a number connected by QR code never has one.

Campaign names, and when they are null

The response carries the ad’s readable names — adName, adsetName, campaignName and their ids — beside the raw sourceId.
The five name fields come back null until the advertiser shares their ad account with us, and null is a supported state, not an error.Reading an ad’s name requires the ads_read permission on the advertiser’s own ad account. The way that is granted is Meta partner sharing: the advertiser adds the Pilot Status business portfolio as a partner on the ad account, with “view performance”. Until they do, the conversation is still counted and sourceId is still returned — only the names are null.Nothing you can send on the request changes this. Querying never triggers a call to the Marketing API: resolution runs asynchronously, off the request path, so a null you see now can be a name a few minutes later with nothing done on your side.
namesResolvedAt is what makes the three states distinguishable, and they call for different answers:
sourceId stays the identity even when the names are present. A campaign can be renamed in Meta at any time, so anything that keys on campaignName loses its own history the first time the advertiser edits it.

Only the first message carries the origin

Meta sends the referral on the first message of a conversation and never repeats it. So a row in GET /v1/referrals amounts to one conversation that started from that ad, and conversations in the summary is the number your ad actually produced.

GET /v1/referrals

Query parameters

string
Restrict to a single ad/post id.
string
ad or post. Anything else returns 400 INVALID_SOURCE_TYPE.
string
ISO 8601 datetime. Messages on or after this instant.
string
ISO 8601 datetime. Messages on or before this instant.
integer
default:"1"
Page number (≥ 1).
integer
default:"30"
Results per page (1–100).

The referral object

Example

GET /v1/referrals/summary

Groups the same data by ad, ordered by conversation count descending.

Query parameters

string
ISO 8601 datetime.
string
ISO 8601 datetime.

Counting rule

conversations counts distinct conversations; messages counts messages. Two messages in the same conversation are 1 conversation and 2 messages — so conversations is the lead count and messages is the volume. lastSourceUrl and lastHeadline come from the newest message for that ad: ad copy can be edited, and the last version Meta sent is the useful one.

The cap is reported, not hidden

The list stops at 200 ads. When there are more, truncated is true and a notice says so — narrow the window with startDate/endDate instead of paging.

Example

Privacy (PII) mode effect

The ad identity and the ad copy are treated differently on purpose: an ad id identifies a creative, never a person, while ctwaClid is a per-click identifier tied to the individual and the headline/body are content. That split is why ad attribution still works on a RELAY_ONLY number instead of returning nothing.

Errors