WhatsApp Flows
whatsapp.flows.* events are special: Platica not only notifies you that something happened, it also expects your response to continue the WhatsApp flow.
Use them when you want your backend to drive dynamic screens, validations, dropdown options, prices, confirmations, or any data that must be computed in real time.
Your endpoint must respond with a valid JSON in under 700 ms. If it doesn't reply in time, Platica returns a fallback { "data": { "acknowledged": true } } to Meta.
Events
| Event | When it happens |
|---|---|
whatsapp.flows.init | The user opens the flow (action: "INIT") |
whatsapp.flows.screen_advance | The user moves to the next screen (action: "data_exchange") |
whatsapp.flows.back | The user returns to the previous screen (action: "BACK") |
whatsapp.flows.exchanges | Umbrella subscription to receive all three above on a single webhook |
Delivery cycle
Payload received
{
"id": "8f2c4d6a-1b3e-4f5d-8a9c-2e7b6d1a3f4e",
"event": "whatsapp.flows.screen_advance",
"workspaceId": "ws_456",
"timestamp": "2026-05-06T19:00:30.000Z",
"source": "whatsapp.flows",
"resourceType": "flow_response",
"resourceId": "ZmxvdyQwMQ==",
"changes": null,
"data": {
"client": {
"id": "client_123",
"phoneNumber": "+5215555555555",
"creationDate": "2026-05-01T12:00:00.000Z",
"lastUpdate": "2026-05-06T18:59:00.000Z",
"name": "Juan Pérez",
"firstname": "Juan",
"email": "juan@empresa.com"
},
"conversation": {
"id": "conv_123",
"conversationId": "conv_123",
"canSendDirectMessage": true,
"workspaceId": "ws_456",
"channelId": "channel_wa_123",
"contactName": "Juan Pérez",
"phoneNumber": "+5215555555555",
"topic": "",
"platform": "whatsapp",
"owners": [],
"tags": [],
"creationDate": "2026-05-06T18:58:00.000Z",
"lastUpdate": "2026-05-06T19:00:00.000Z",
"status": "open",
"operation": "automatic",
"messageCount": 3
},
"flow": {
"id": "flow_doc_abc",
"wabaId": "1234567890",
"name": "Quote flow",
"status": "PUBLISHED",
"categories": ["LEAD_GENERATION"],
"dataApiVersion": "3.0",
"endpointUri": "https://app.platica.mx/api/whatsapp/flows/1234567890/data",
"jsonVersion": "7.1",
"creationDate": "2026-05-01T12:00:00.000Z",
"lastUpdate": "2026-05-06T18:55:00.000Z",
"lastSyncAt": "2026-05-06T18:55:00.000Z"
},
"flowResponse": {
"flowToken": "ZmxvdyQwMQ==",
"status": "IN_PROGRESS",
"sentAt": "2026-05-06T18:59:00.000Z",
"openedAt": "2026-05-06T19:00:00.000Z",
"lastActivityAt": "2026-05-06T19:00:30.000Z",
"completedAt": null,
"expiresAt": "2026-05-07T18:59:00.000Z",
"clientId": "client_123",
"conversationId": "conv_123",
"messageId": "wamid.HBgN...",
"campaignId": null,
"agentId": null,
"channelId": "channel_wa_123"
},
"flowExchange": {
"action": "data_exchange",
"screen": "WELCOME",
"version": "3.0",
"payload": {
"selected_product": "p2",
"email": "juan@empresa.com"
}
}
}
} | Field | Type | Description |
|---|---|---|
event | string | Event received |
id | string | Delivery UUID |
workspaceId | string | Owning workspace |
timestamp | string | ISO-8601 delivery timestamp |
source | whatsapp.flows | Stable event source |
resourceType | flow_response | Type of affected resource |
resourceId | string \| null | Meta flow_token when present |
changes | null | Reserved for compatibility with the common envelope |
data.client | object \| null | Associated customer summary, if Platica could resolve it |
data.conversation | object \| null | Associated conversation, if there is a conversationId |
data.flow | object | Normalized flow snapshot |
data.flowResponse | object \| null | Flow response/session state |
data.flowExchange | object | Exchange data received from Meta |
The data Meta sends in decryptedBody.data now lives in data.flowExchange.payload. The current screen is in data.flowExchange.screen, the session token is in resourceId and data.flowResponse.flowToken, and the internal flow ID is in data.flow.id.
data.flowExchange fields
| Field | Type | Description |
|---|---|---|
action | init \| data_exchange \| back | Meta action normalized to lowercase |
screen | string \| null | Current screen sent by Meta |
version | string \| null | WhatsApp Flows protocol version |
payload | object | Data captured or sent by Meta for this step |
Expected response
Your endpoint must respond with 2xx and JSON. Platica accepts the response if it meets at least one of the following:
- It has
data, anddatais an object. - It has
screen, andscreenis a non-empty string.
Example for populating dynamic data:
{
"data": {
"products": [
{ "id": "p1", "title": "Plan Básico" },
{ "id": "p2", "title": "Plan Premium" }
]
}
} Example to advance to a screen:
{
"screen": "PAYMENT",
"data": {
"amount_mxn": 499
}
} Example to show an error:
{
"data": {
"error_message": "El código postal no es válido"
}
} Headers and signature
| Header | Description |
|---|---|
Content-Type | application/json |
User-Agent | Platica-Webhooks/1.0 |
X-Webhook-Event | Matches payload.event |
X-Webhook-Id | Identifier of the configured webhook |
X-Webhook-Event-Id | Matches payload.id |
X-Webhook-Resource-Type | flow_response |
X-Webhook-Resource-Id | Matches payload.resourceId |
X-Webhook-Timestamp | Matches payload.timestamp |
X-Webhook-Signature | sha256=<hex> if you configured secret |
The signature is computed with HMAC-SHA256 over the raw body:
sha256=<hmac_sha256(secret, rawBody)> Node.js example:
const crypto = require('crypto');
function verifyFlowsWebhook(secret, rawBody, signatureHeader) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
} whatsapp.flows.init
Happens when the user opens the flow. Usually used to preload initial data.
{
"id": "5e3a1b9c-7d12-4a8e-9c5f-1e6b8d0a2c4f",
"event": "whatsapp.flows.init",
"workspaceId": "ws_456",
"timestamp": "2026-05-06T19:00:00.000Z",
"source": "whatsapp.flows",
"resourceType": "flow_response",
"resourceId": "ZmxvdyQwMQ==",
"changes": null,
"data": {
"client": null,
"conversation": null,
"flow": {
"id": "flow_doc_abc",
"wabaId": "1234567890"
},
"flowResponse": {
"flowToken": "ZmxvdyQwMQ==",
"status": "IN_PROGRESS"
},
"flowExchange": {
"action": "init",
"screen": null,
"version": "3.0",
"payload": {}
}
}
} whatsapp.flows.screen_advance
Happens when the user fills out a screen and taps continue. data carries the captured values.
{
"id": "8f2c4d6a-1b3e-4f5d-8a9c-2e7b6d1a3f4e",
"event": "whatsapp.flows.screen_advance",
"workspaceId": "ws_456",
"timestamp": "2026-05-06T19:00:30.000Z",
"source": "whatsapp.flows",
"resourceType": "flow_response",
"resourceId": "ZmxvdyQwMQ==",
"changes": null,
"data": {
"client": null,
"conversation": null,
"flow": {
"id": "flow_doc_abc",
"wabaId": "1234567890"
},
"flowResponse": {
"flowToken": "ZmxvdyQwMQ==",
"status": "IN_PROGRESS"
},
"flowExchange": {
"action": "data_exchange",
"screen": "WELCOME",
"version": "3.0",
"payload": {
"selected_product": "p2",
"email": "juan@empresa.com"
}
}
}
} whatsapp.flows.back
Happens when the user taps back. Use it to restore the data of a previous screen.
{
"id": "3a5b7c9d-2e4f-4a6b-8c1d-5e7f9a2b4c6d",
"event": "whatsapp.flows.back",
"workspaceId": "ws_456",
"timestamp": "2026-05-06T19:01:00.000Z",
"source": "whatsapp.flows",
"resourceType": "flow_response",
"resourceId": "ZmxvdyQwMQ==",
"changes": null,
"data": {
"client": null,
"conversation": null,
"flow": {
"id": "flow_doc_abc",
"wabaId": "1234567890"
},
"flowResponse": {
"flowToken": "ZmxvdyQwMQ==",
"status": "IN_PROGRESS"
},
"flowExchange": {
"action": "back",
"screen": "PAYMENT",
"version": "3.0",
"payload": {}
}
}
} whatsapp.flows.exchanges
An umbrella subscription to receive init, screen_advance, and back on a single webhook.
Use whatsapp.flows.exchanges if your backend wants to handle every flow step with the same endpoint and the same logic.
If you need separate logic per step, subscribe to the granular events:
whatsapp.flows.initwhatsapp.flows.screen_advancewhatsapp.flows.back