Sandbox
Inject synthetic inbound messages so your integration can exercise the receive path without a real Meta webhook.
Quickstart diagnostics
Response
| Field | Type | Notes |
|---|---|---|
objectrequired | "sandbox_quickstart" | |
environmentrequired | object | |
api_keyrequired | object | |
senderrequired | object | |
webhooksrequired | object | |
capabilitiesrequired | object | |
missing_scopesrequired | string[] |
Simulate inbound
This is the canonical sandbox inbound endpoint. Use it with a tx_sandbox_... key that has sandbox:write. For WhatsApp service-window tests, set from to the customer phone you will later send to, and set to to the sandbox sender id used by the outbound request.
curl "$TYXTER_API_BASE_URL/v1/sandbox/inbound-messages" \
-H "authorization: Bearer $TYXTER_API_KEY" \
-H "content-type: application/json" \
-H "tyxter-trace-id: diag_open_window" \
-d '{
"channel": "whatsapp",
"from": "+15551234567",
"to": "sandbox_sender",
"type": "text",
"text": { "body": "Opening the sandbox service window" }
}'Request body
| Field | Type | Notes |
|---|---|---|
channel | "whatsapp" | "instagram" | "whatsapp_channel" | |
fromrequired | string | len 1..∞ |
torequired | string | len 1..∞ |
typerequired | "text" | "media" | "interactive" | "flow" | |
text | object | |
media | object | |
interactive | object | |
flow | object | |
metadata | object |
Response
| Field | Type | Notes |
|---|---|---|
idrequired | string | |
objectrequired | "message" | |
directionrequired | "inbound" | |
channelrequired | "whatsapp" | "instagram" | "whatsapp_channel" | |
typerequired | string | |
statusrequired | string | |
environmentrequired | "sandbox" | "production" | |
senderrequired | object | |
recipientrequired | object | |
providerrequired | string | null | |
provider_message_idrequired | string | null | |
payloadrequired | unknown | |
trace_idrequired | string | |
created_atrequired | string<ISO-8601> | pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ |
Simulate Flow completion
To prove the flow.completed webhook without a real Meta submission, drive an outbound flow send through the flow_completed scenario. POST /v1/sandbox/inbound-messages with type: "flow" only emits message.received; it does not emit flow.completed. The supported path is a normal POST /v1/messages flow send tagged with metadata.sandbox_scenario = "flow_completed".
flow.action.parameters.flow_idequals the published flow's provider_flow_id — the value from GET /v1/flows/{id} after publish, not the Tyxter flw_... id. The sandbox emits a provider-shaped provider.flow_completion event keyed on that id, and the flow worker looks the flow up by provider_flow_id to persist the FlowSubmission. Send the wrong id and no flow.completed ever fires.Full five-step sequence with a sandbox key:
POST /v1/flows— create a flow.POST /v1/flows/{id}/publish— publish it (sandbox auto-publishes locally and assigns aprovider_flow_id).GET /v1/flows/{id}— readprovider_flow_idfrom the response.POST /v1/messageswith the flow message below.GET /v1/webhook-events/listen?event_type=flow.completed— read the completion proof.
curl "$TYXTER_API_BASE_URL/v1/messages" \
-H "authorization: Bearer $TYXTER_API_KEY" \
-H "content-type: application/json" \
-d '{
"channel": "whatsapp",
"sender": { "type": "whatsapp_phone_number", "id": "sandbox_sender" },
"recipient": { "type": "phone_e164", "id": "+15551234567" },
"message": {
"type": "flow",
"flow": {
"body": { "text": "Tap below to complete your booking." },
"action": {
"name": "flow",
"parameters": {
"flow_id": "<provider_flow_id from GET /v1/flows/{id}>",
"flow_token": "booking-7f3a",
"flow_cta": "Book now"
}
}
}
},
"metadata": {
"sandbox_scenario": "flow_completed",
"sandbox_flow_response": { "email": "[email protected]", "size": "M" }
}
}'metadata.sandbox_flow_response is optional and controls the submitted response data; it defaults to { "sandbox": true }. The listen response returns the same flow.completed data shape a real Meta submission produces:
{
"object": "webhook_event_listen",
"data": [
{
"type": "flow.completed",
"payload": {
"type": "flow.completed",
"data": {
"flow_id": "flw_...",
"name": "lead_capture",
"status": "published",
"provider_flow_id": "1234567890",
"rejection_reason": null,
"submission": {
"id": "sub_...",
"provider_token": "booking-7f3a",
"response": { "email": "[email protected]", "size": "M" },
"message_id": "msg_...",
"received_at": "2026-04-27T12:05:00.000Z"
}
}
}
}
]
}submission.provider_token echoes the flow_token you sent, so you can correlate the completion back to your own record without an inbound Meta webhook.