Follow-ups are a beta. When the beta is offered to your team, a team owner or
admin selects Join on the Receipt-timed campaign follow-ups beta row in the
campaign builder’s
message step, reviews the terms and selects Accept and join. Until your
team has joined and the beta is available to it, requests that set a
follow-up, or launch or copy a campaign that has one, answer
403 feature_unavailable. Removing a follow-up from a draft always works.How the timing works
- Delivery starts the wait. The follow-up is due its delay after WhatsApp first reports the first message as delivered.
- A read takes precedence. If WhatsApp reports the first message as read before the follow-up starts sending, the wait restarts from that first read. Later reads do not move it again.
- No receipt, no follow-up. A follow-up never sends on a timer alone. Recipients who turned off read receipts report deliveries only, so their wait starts from delivery.
- Expiry never sends. The expiry counts from when the first message was
sent. A follow-up that is not sent by then is marked
expiredand never sent.
The campaign’s send window also applies:
a follow-up that falls due outside the window waits for the next opening, as
long as it has not expired.
What cancels a follow-up
- The recipient replies to the number that sent the first message.
- The phone number is added to your opt-out list, whether by a STOP keyword, the API, an import or the console. A later START does not restore a cancelled follow-up.
- You stop the campaign.
- The sending number changes: it is renamed, connected again as a new connection, or switches sending type. A follow-up is only ever sent from the original number and connection.
- Your team leaves the beta, or the beta is turned off.
outcome_unknown until it can prove the
outcome.
Requirements
- The campaign sends one first message: a single composed message or a template. A campaign with a sequence of composed messages cannot launch with a follow-up.
- The follow-up is either text (1 to 16,384 characters, with the same
{{variable}}placeholders as the campaign) or a message template (templateId). Follow-up text cannot contain tracked links ({{link:key}}). - On a Cloud API number, the follow-up follows the same 24-hour window rule as any composed message: outside it, a text follow-up fails. Use an approved template to reach recipients outside that window.
- The follow-up fills its variables the same way as the first message,
including the campaign’s variable mapping. A recipient without a value for a
variable that has no inline fallback does not get the follow-up; it ends as
failedwith reasonmissing_variable. The console accepts only variables that the first message or its mapping already uses. - You can add, change or remove the follow-up only while the campaign is an unlaunched draft. Launch freezes it.
- A/B tests and weighted variations work with follow-ups: each recipient’s own first message starts their wait.
Add a follow-up
Use the console
In the campaign builder’s message step, turn on Send one follow-up, write the message or choose a message template, and set how long to wait and when to stop waiting. The review step shows the follow-up before you launch.Use the API
SendfollowUp when you create or update a draft:
"message": { "type": "template", "templateId": "<template UUID>" }. Send
"followUp": null to remove the follow-up. On the Platform API, send the same
field to POST /platform/campaigns or PATCH /platform/campaigns/{campaignId}.
A follow-up the API refuses answers 400 invalid_parameter with
param: "followUp". Launching a campaign that combines a follow-up with a
message sequence answers 409 state_conflict.
Read follow-up results
Follow-up results are counted apart from the first message. The first-message counts and webhooks keep their meaning.Campaign analytics
GET /messaging/projects/{projectSlug}/campaigns/{id}/analytics and
GET /platform/campaigns/{campaignId}/analytics return a followUps object:
All counts are zero for a campaign without a follow-up.
Recipients
Each recipient fromGET /messaging/projects/{projectSlug}/campaigns/{id}/recipients
or GET /platform/campaigns/{campaignId}/recipients has a followUp object,
or null when no follow-up was created for that recipient. It includes
status, anchorKind (delivered or read), firstDeliveredAt,
firstReadAt, dueAt, expiresAt, sentAt, deliveredAt, readAt,
repliedAt, reason and commandId. Times are Unix milliseconds.
status is one of waiting_receipt, scheduled, sending, sent,
cancelled, expired, failed or outcome_unknown. reason explains a
cancellation, expiry, failure or wait, for example reply, opt_out,
wait_expired, campaign_stop, sender_changed, feature_withdrawn,
send_window, sender_unavailable or missing_variable.
The recipient CSV export adds the columns followUpStatus, followUpAnchor,
followUpDueAt, followUpExpiresAt, followUpSentAt, followUpReadAt,
followUpRepliedAt, followUpReason and followUpCommandId.
Campaign history
GET /platform/campaigns/{campaignId}/events records each follow-up state
change as a followup.<status> event, such as followup.scheduled or
followup.cancelled, with the recipient, the sending number and the reason.
It never contains message text or phone numbers. The same state changes
appear in the Console’s Logs as campaign.followup.<status>
rows. Follow-ups do not add webhook events.