Flows
Interactive WhatsApp forms hosted by Meta. Store the JSON definition with Tyxter, publish to Meta, and receive every end-user submission as a signed webhook.
Create
curl https://api.tyxter.com/v1/flows \
-H "authorization: Bearer $TYXTER_API_KEY" \
-H "content-type: application/json" \
-d '{
"name": "lead_capture",
"flow_json": {
"version": "6.3",
"screens": [
{
"id": "WELCOME",
"title": "Welcome",
"terminal": true,
"layout": { "type": "SingleColumnLayout", "children": [] }
}
]
}
}'We validate the JSON structurally before storage — unsupported versions, missing screens, missing terminal screens, and duplicate screen ids are rejected with 400 invalid_flow_json. Meta will do its own semantic validation at publish time.
Send
Send a published flow to a recipient with the channel-native messages route, using message.type = "flow". Address the flow with flow.action.parameters.flow_id — the published flow's provider_flow_id, which you read from GET /v1/flows/{id} once publish completes — or with flow.action.parameters.flow_name; provide exactly one. flow_token is your required opaque correlation token: it comes back to you as submission.provider_token on the flow.completed webhook, so use it to tie a completion back to your own record. flow_cta is the required button label (max 30 chars).
curl https://api.tyxter.com/v1/messages \
-H "authorization: Bearer $TYXTER_API_KEY" \
-H "content-type: application/json" \
-d '{
"channel": "whatsapp",
"sender": { "type": "whatsapp_phone_number", "id": "pn_..." },
"recipient": { "type": "phone_e164", "id": "+5511999999999" },
"message": {
"type": "flow",
"flow": {
"body": { "text": "Tap below to complete your booking." },
"action": {
"name": "flow",
"parameters": {
"flow_id": "1234567890",
"flow_token": "booking-7f3a",
"flow_cta": "Book now"
}
}
}
}
}'Publish
Snapshots the current flow_json into a new FlowVersion and enqueues the Meta sync worker. Returns 202.
139000 rejection is account-scoped; Tyxter may append Account health entries with their numeric code plus any provider-supplied entity, description, or remediation. See Troubleshooting.Troubleshooting
A rejected publish archives the flow. For a Meta-originated rejection, rejection_reasonpreserves Meta's inline explanation and may add the account-health context described below; it is readable on GET /v1/flows/{id} and attached to the flow.rejected webhook. rejection_reason is diagnostic free text that may contain platform text, the inline Meta explanation, and Tyxter-formatted provider health context; branch on Flow status and never parse the string. The common rejections:
Account-integrity rejection: 139000
Meta can return Integrity requirements not met. with numeric Graph code 139000. Numeric Meta error 139000 is account-scoped, not a single diagnosis. When Meta exposes Account health entries, Tyxter appends their numeric code plus any provider-supplied entity, description, or remediation to rejection_reason. Follow whichever provider fields are present.
Business verification is a confirmed common Flow-publish gate, and a payment method does not substitute for it; a WABA 141006 payment-method health issue can be adjacent business-initiated messaging or registration context rather than the Flow-publish cause. For example, a provider-supplied BUSINESS (141010) description or remediation can direct you to Meta Business Suite under Settings → Business verification. A rejected or archived Flow is terminal for customer-initiated publish and retry paths; fix the Meta account state, then create or clone and publish a fresh Flow instead of relying on an in-place retry.
Validation errors: …
Meta's semantic validation rejected the flow body. We validate structure before storage, but Meta applies deeper rules at publish time; when it does, the rejection reason appends up to five of Meta's specific complaints, for example Required property '__example__' is missing. or Screen "SUMMARY" is unreachable. Fix the flow_json in a new flow (or dashboard clone) and publish that one.
provider_missing_since is set
Meta no longer resolves the stored provider_flow_id. Tyxter preserves the last known lifecycle status because a temporary permission problem can look like a deleted Flow, records the first observed time in provider_missing_since, and clears the marker after a later successful provider read. Check the Flow in Meta Business Suite and reconnect the WhatsApp account if the Flow should still exist.
Republish returns 409 flow_archived
An archived Flow cannot be re-published through the customer API. Fix the Meta account state, then create a new Flow with the corrected flow_json — or clone in the dashboard, which resets the provider state — and publish the fresh Flow instead of relying on an in-place retry.
Completions
In production, Meta sends an end-user's reply in its ordinary messages callback as interactive.nfm_reply, not in a Flow lifecycle callback. Tyxter preserves that ordinary inbound message and independently derives a FlowSubmission (idempotent on flow_token) plus the same signed flow.completed webhook for your endpoints. The inbound-message and completion paths use separate queues, so do not rely on durable processing or customer delivery order between message.received and flow.completed.
{
"id": "evt_...",
"type": "flow.completed",
"data": {
"flow_id": "flw_...",
"name": "lead_capture",
"status": "published",
"provider_flow_id": "1234567890",
"rejection_reason": null,
"submission": {
"id": "sub_...",
"provider_token": "flow-token-xyz",
"response": { "email": "user_at_example_dot_com", "size": "M" },
"message_id": "msg_...",
"received_at": "2026-04-27T12:05:00.000Z"
}
}
}Every flow.completed data carries the correlation fields you need to tie a submission back: flow_id (the Tyxter flw_... id), provider_flow_id (the Meta flow id the message was sent with), submission.id, submission.provider_token (the flow_token you sent), submission.response (the submitted fields), and submission.message_id. The parsed response never includes a top-level flow_token; use submission.provider_token and the source submission.message_id to correlate the completion.
Lifecycle events
flow.published— publish succeeded.flow.rejected— Meta rejected;rejection_reasonattached.flow.completed— end-user submitted the form.