API reference

Sandbox

Inject synthetic inbound messages so your integration can exercise the receive path without a real Meta webhook.

Quickstart diagnostics

GET/v1/sandbox/quickstart

Response

FieldTypeNotes
objectrequired"sandbox_quickstart"
environmentrequiredobject
api_keyrequiredobject
senderrequiredobject
webhooksrequiredobject
capabilitiesrequiredobject
missing_scopesrequiredstring[]

Simulate inbound

POST/v1/sandbox/inbound-messages202 Accepted

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

FieldTypeNotes
channel"whatsapp" | "instagram" | "whatsapp_channel"
fromrequiredstring
len 1..∞
torequiredstring
len 1..∞
typerequired"text" | "media" | "interactive" | "flow"
textobject
mediaobject
interactiveobject
flowobject
metadataobject

Response

FieldTypeNotes
idrequiredstring
objectrequired"message"
directionrequired"inbound"
channelrequired"whatsapp" | "instagram" | "whatsapp_channel"
typerequiredstring
statusrequiredstring
environmentrequired"sandbox" | "production"
senderrequiredobject
recipientrequiredobject
providerrequiredstring | null
provider_message_idrequiredstring | null
payloadrequiredunknown
trace_idrequiredstring
created_atrequiredstring<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".

Completion only resolves when 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:

  1. POST /v1/flows — create a flow.
  2. POST /v1/flows/{id}/publish — publish it (sandbox auto-publishes locally and assigns a provider_flow_id).
  3. GET /v1/flows/{id} — read provider_flow_id from the response.
  4. POST /v1/messages with the flow message below.
  5. 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.