Responses and Webhooks
When a contact submits a flow, WhatsApp sends the answers back as a message in the chat. SendSeven:
- Matches the answer to the session it belongs to, using the flow token from the send. The answers are always stored on the contact the flow was sent to.
- Stores the answers on the session and shows the reply in the conversation.
- Writes back mapped answers to the contact (field mapping).
- Notifies you: the
whatsapp_flow.completedwebhook, the waiting Send WhatsApp Flow node, and thewhatsapp_flow.completedtrigger.
Each session completes at most once. A redelivered answer from WhatsApp is ignored.
Flows that were built outside SendSeven (for example in WhatsApp Manager and imported) or sent before you used SendSeven still arrive. SendSeven creates an adopted session for them (sender_type: "adopted"), without write-back mappings.
Write-back result
Every completion carries a writeback object:
{
"state": "partial",
"contact_fields": { "first_name": "Anna", "email": "[email protected]" },
"custom_fields": [
{ "custom_field_id": "3f1c…", "key": "kundennummer", "status": "ok" }
],
"tags": [{ "tag_id": "b7d2…", "status": "ok" }],
"subscriptions": [{ "list_id": "a9c4…", "status": "ok" }],
"unsubscriptions": [{ "list_id": "d2e8…", "status": "ok" }],
"skipped": [
{ "input": "email", "target": "contact_field", "field": "email", "reason": "owned_by_other_contact" }
],
"media": {
"invoice_upload": [
{ "file_name": "rechnung.pdf", "mime_type": "application/pdf", "stored": true, "file_size": 48213 }
]
}
}
state | Meaning |
|---|---|
done | Everything that was mapped was written. |
partial | Some items were skipped or failed. Check skipped and the item statuses. |
error | The write-back failed. The answers are still stored and delivered. |
skipped | There was no contact to write to. |
Item statuses:
| Key | Statuses |
|---|---|
custom_fields | ok, not_found, not_editable, invalid (with reason) |
tags | ok, not_found, error |
subscriptions | ok, not_found, list_type_not_allowed, not_offered, error |
unsubscriptions | ok, skipped (with reason), error |
skipped[].reason | e.g. consent_required, owned_by_other_contact, invalid_email, invalid_date, invalid_language |
Uploaded photos and documents are copied into SendSeven's storage and listed under media, keyed by input name.
Sessions API
GET /whatsapp-flows/{id}/sessions lists every send of a flow with its answers, newest first. Scope: whatsapp_flows:read.
| Query | Notes |
|---|---|
page, page_size | Pagination, page_size up to 100 (default 25). |
status | Filter by session status (sent, completed, …). |
contact_id | Only sessions of one contact. |
curl "https://api.sendseven.com/api/v1/whatsapp-flows/0f9e8d7c-…/sessions?status=completed&page_size=50" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"
{
"items": [
{
"id": "5b0e2c1a-…",
"definition_id": "0f9e8d7c-…",
"version_id": "a1b2c3d4-…",
"version_number": 3,
"contact_id": "c0ffee00-…",
"contact_name": "Anna Schmidt",
"conversation_id": "9a8b7c6d-…",
"channel_id": "8c1d0f5e-…",
"sender_type": "automation",
"status": "completed",
"last_screen": "CONTACT",
"answers": { "callback_time": "vormittags", "phone_ok": true, "customer_number": "••••" },
"writeback_result": { "state": "done", "contact_fields": { "first_name": "Anna" } },
"last_error": null,
"created_at": "2026-10-03T09:15:00Z",
"opened_at": "2026-10-03T09:16:10Z",
"completed_at": "2026-10-03T09:17:02Z",
"expires_at": "2026-10-04T09:15:00Z"
}
],
"pagination": { "page": 1, "page_size": 50, "total": 1, "total_pages": 1 }
}
Answers of fields listed in a screen's sensitive list are masked as ••••.
sender_type tells you where the send came from: inbox, campaign, automation, api or adopted.
Webhook events
Subscribe a webhook endpoint to these events. They use the standard envelope:
{
"id": "evt_8f2a1c3b4d5e6f70",
"type": "whatsapp_flow.completed",
"event_id": "5b0e2c1a-…",
"created_at": "2026-10-03T09:17:03Z",
"tenant_id": "tnt_…",
"data": { … }
}
whatsapp_flow.completed
Sent once per completed session. event_id is the session id, so you can de-duplicate on it.
{
"type": "whatsapp_flow.completed",
"event_id": "5b0e2c1a-…",
"data": {
"session_id": "5b0e2c1a-…",
"flow_definition_id": "0f9e8d7c-…",
"flow_name": "Rückruf anfordern",
"version_id": "a1b2c3d4-…",
"meta_flow_id": "1234567890123456",
"channel_id": "8c1d0f5e-…",
"contact_id": "c0ffee00-…",
"conversation_id": "9a8b7c6d-…",
"message_id": "msg_91ab…",
"sender_type": "api",
"answers": {
"first_name": "Anna",
"callback_time": "vormittags",
"newsletter": true
},
"writeback": {
"state": "done",
"contact_fields": { "first_name": "Anna" },
"subscriptions": [{ "list_id": "a9c4…", "status": "ok" }]
},
"completed_at": "2026-10-03T09:17:02Z"
}
}
answers contains every input by its name, including unmapped ones. Answers of fields listed in a screen's sensitive list are masked as ••••, as in the sessions API, so those values do not leave SendSeven. If your system needs them, send them to your own data source webhook instead. Values keep WhatsApp's types: strings, booleans for OptIn, arrays of option ids for CheckboxGroup, dates as strings.
Lifecycle events
whatsapp_flow.status_changed and whatsapp_flow.health_changed report changes that WhatsApp makes to a published flow. Their event_id is <publication id>:<hash>: unique for every change WhatsApp reports, and the same when WhatsApp delivers one change twice. De-duplicate on event_id.
whatsapp_flow.status_changed
{
"type": "whatsapp_flow.status_changed",
"event_id": "pub_3c9e…:9b1f0c2d3e4a5b6c",
"data": {
"channel_id": "8c1d0f5e-…",
"waba_id": "102938475610293",
"flow": { "id": "0f9e8d7c-…", "name": "Terminbuchung", "meta_flow_id": "1234567890123456" },
"message": "Flow Terminbuchung changed status from PUBLISHED to THROTTLED",
"old_status": "PUBLISHED",
"new_status": "THROTTLED"
}
}
Statuses: DRAFT, PUBLISHED, DEPRECATED, BLOCKED, THROTTLED (see Publishing).
whatsapp_flow.health_changed
Only relevant for dynamic flows: WhatsApp monitors the data endpoint and raises or clears alerts.
{
"type": "whatsapp_flow.health_changed",
"event_id": "pub_3c9e…:9b1f0c2d3e4a5b6c",
"data": {
"channel_id": "8c1d0f5e-…",
"waba_id": "102938475610293",
"flow": { "id": "0f9e8d7c-…", "name": "Terminbuchung", "meta_flow_id": "1234567890123456" },
"message": "Endpoint latency is above the threshold",
"alert": {
"event": "ENDPOINT_LATENCY",
"alert_state": "ACTIVATED",
"alert_severity": "WARNING",
"p90_latency": 7200,
"threshold": 7000
},
"health_status": "RED"
}
}
alert.event | Meaning |
|---|---|
ENDPOINT_ERROR_RATE | Too many failed data requests. |
ENDPOINT_LATENCY | Data requests are too slow (p50_latency, p90_latency). |
ENDPOINT_AVAILABILITY | The endpoint was not reachable. |
CLIENT_ERROR_RATE | Too many errors on the contacts' phones. |
health_status is RED while any alert is ACTIVATED, and GREEN once all are DEACTIVATED. Metric fields (error_rate, threshold, p50_latency, p90_latency, requests_count, availability, errors) are present when WhatsApp sends them. If a dynamic flow keeps failing, check your webhook data source timeouts first; WhatsApp may block a flow whose endpoint stays unhealthy.