If you set callback.url in the send request (POST /otp/send-failover), the service POSTs JSON to that URL when delivery status changes or when failover is sent. Your endpoint should respond with 2xx quickly. Webhooks are sent in a fire-and-forget manner; the service does not retry.

6.1 Webhook payload = tracking response

Every webhook POST body has the same JSON shape as the response of GET /otp/failover/{flowId}. There are no separate event types or payload formats: you receive the full flow state (flowId, status, statusCode, total_cost, steps) each time we notify you. You can use one handler that parses this structure and updates your UI or logic.

The initial flow state (e.g. one step, status queued) is not sent by webhook—it is returned in the POST /otp/send-failover response body (and you can also GET /otp/failover/{flowId} at any time). Webhooks are sent only when something changes after that (e.g. a step’s status changes, or a failover step is added). The service does not send duplicate webhooks when the reported status is unchanged.

When we POST
After a delivery’s status is updated (e.g. Viber: DELIVERED, EXPIRED; SMS: Queued, Sent, Delivered).
After the OTP is sent via the next channel (failover sent, e.g. SMS after Viber).

Payload fields match §5.1: flowId, status, statusCode, total_cost, steps (each step: trackingId, role, flowId, status, channel, phone_number, label, created_at, sent_at, delivered_at, detailed_status, error_message, failover_from_message_token, price, latency_ms, country, operator).

6.2 Example: Viber → SMS flow (same flowId)

The following examples show one Viber with SMS fallback flow. The flowId is the same; status, total_cost, and steps change over time. The first state is what you get from the send response (or GET tracking); the rest are webhook payloads.

Initial state – from POST /otp/send-failover response (one step, Viber queued)
This shape is not POSTed to your callback; it is in the API response body.

{
  "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "InProgress",
  "statusCode": null,
  "total_cost": null,
  "steps": [
    {
      "trackingId": "6202325346262658140",
      "role": "primary",
      "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "queued",
      "channel": "viber",
      "phone_number": "306912345671",
      "label": "login-otp",
      "created_at": "2026-02-16T12:00:00Z",
      "sent_at": null,
      "delivered_at": null,
      "detailed_status": null,
      "error_message": null,
      "failover_from_message_token": null,
      "price": null,
      "latency_ms": null,
      "country": null,
      "operator": null
    }
  ]
}

Webhook 1 – After Viber delivered (one step, total_cost set)

{
  "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "InProgress",
  "statusCode": null,
  "total_cost": 0.0093,
  "steps": [
    {
      "trackingId": "6202325346262658140",
      "role": "primary",
      "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "delivered",
      "channel": "viber",
      "phone_number": "306912345671",
      "label": "login-otp",
      "created_at": "2026-02-16T12:00:00Z",
      "sent_at": "2026-02-16T12:00:01Z",
      "delivered_at": "2026-02-16T12:00:02Z",
      "detailed_status": "Delivered",
      "error_message": null,
      "failover_from_message_token": null,
      "price": 0.0093,
      "latency_ms": 2100,
      "country": null,
      "operator": null
    }
  ]
}

Webhook 2 – After SMS failover sent (two steps: Viber expired, SMS sent)

{
  "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "InProgress",
  "statusCode": null,
  "total_cost": 0.0468,
  "steps": [
    {
      "trackingId": "6202325346262658140",
      "role": "primary",
      "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "expired",
      "channel": "viber",
      "phone_number": "306912345671",
      "label": "login-otp",
      "created_at": "2026-02-16T12:00:00Z",
      "sent_at": "2026-02-16T12:00:01Z",
      "delivered_at": null,
      "detailed_status": "Expired",
      "error_message": null,
      "failover_from_message_token": null,
      "price": 0.0093,
      "latency_ms": null,
      "country": null,
      "operator": null
    },
    {
      "trackingId": "sms-abc-def-456",
      "role": "failover",
      "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "queued",
      "channel": "sms",
      "phone_number": "306912345671",
      "label": "login-otp",
      "created_at": "2026-02-16T12:02:00Z",
      "sent_at": null,
      "delivered_at": null,
      "detailed_status": null,
      "error_message": null,
      "failover_from_message_token": "6202325346262658140",
      "price": 0.0375,
      "latency_ms": null,
      "country": null,
      "operator": null
    }
  ]
}

Webhook 3 – After SMS delivered (two steps, flow Succeeded)

{
  "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "Succeeded",
  "statusCode": null,
  "total_cost": 0.0468,
  "steps": [
    {
      "trackingId": "6202325346262658140",
      "role": "primary",
      "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "expired",
      "channel": "viber",
      "phone_number": "306912345671",
      "label": "login-otp",
      "created_at": "2026-02-16T12:00:00Z",
      "sent_at": "2026-02-16T12:00:01Z",
      "delivered_at": null,
      "detailed_status": "Expired",
      "error_message": null,
      "failover_from_message_token": null,
      "price": 0.0093,
      "latency_ms": null,
      "country": null,
      "operator": null
    },
    {
      "trackingId": "sms-abc-def-456",
      "role": "failover",
      "flowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "delivered",
      "channel": "sms",
      "phone_number": "306912345671",
      "label": "login-otp",
      "created_at": "2026-02-16T12:02:00Z",
      "sent_at": "2026-02-16T12:02:01Z",
      "delivered_at": "2026-02-16T12:02:03Z",
      "detailed_status": "Delivered",
      "error_message": null,
      "failover_from_message_token": "6202325346262658140",
      "price": 0.0375,
      "latency_ms": 1800,
      "country": "GR",
      "operator": "Cosmote"
    }
  ]
}

6.3 Webhook requirements

  • Content-Type of requests is application/json.
  • For testing, you can use webhook testing tools; ensure the POST URL you use is the one that receives requests (some tools use different URLs for viewing vs. receiving).

What’s Next