> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hany.tools/llms.txt
> Use this file to discover all available pages before exploring further.

# Delivery Status Webhook: SMS Event Payload Reference

> Full payload reference for sms.delivered, sms.sent, and sms.failed webhook events. Includes field descriptions, example payloads, and a Node.js handler.

Hany fires delivery status webhook events as your SMS messages move through the carrier network. When a carrier confirms delivery — or reports a failure — Hany immediately POSTs the event to the `callback_url` you provided in your original send request. Use these events to update your records, notify your users, or trigger follow-up workflows in real time.

## Event Types

* **`sms.delivered`** — Fired when the carrier confirms the message was successfully delivered to the recipient's handset.
* **`sms.failed`** — Fired when delivery fails after all carrier-level retries are exhausted. The payload includes an `error_code` and `error_message` to help you diagnose the cause.
* **`sms.sent`** — Fired when the message leaves Hany's gateway and is handed off to the carrier network. At this point, delivery is not yet confirmed.

## Payload Examples

The examples below show the payload shapes for each event type.

**Successful delivery (`sms.delivered`)**

```json theme={null}
{
  "event": "sms.delivered",
  "message_id": "msg_abc123xyz",
  "batch_id": null,
  "to": "+15550001234",
  "from": "YourBrand",
  "status": "delivered",
  "delivered_at": "2024-06-01T08:05:23Z",
  "sent_at": "2024-06-01T08:05:10Z",
  "timestamp": "2024-06-01T08:05:25Z"
}
```

**Message dispatched to carrier (`sms.sent`)**

```json theme={null}
{
  "event": "sms.sent",
  "message_id": "msg_abc123xyz",
  "batch_id": null,
  "to": "+15550001234",
  "from": "YourBrand",
  "status": "sent",
  "delivered_at": null,
  "sent_at": "2024-06-01T08:05:10Z",
  "timestamp": "2024-06-01T08:05:11Z"
}
```

## Payload Fields

<ResponseField name="event" type="string" required>
  The type of webhook event. One of `sms.delivered`, `sms.failed`, or `sms.sent`.
</ResponseField>

<ResponseField name="message_id" type="string" required>
  The unique identifier of the SMS message. Use this to correlate the webhook event with the original send request.
</ResponseField>

<ResponseField name="batch_id" type="string | null" required>
  The batch ID if the message was sent via the bulk send endpoint. `null` for single sends.
</ResponseField>

<ResponseField name="to" type="string" required>
  The recipient's phone number in E.164 format.
</ResponseField>

<ResponseField name="from" type="string" required>
  The sender ID used when the message was sent — either a phone number or an alphanumeric brand name.
</ResponseField>

<ResponseField name="status" type="string" required>
  The delivery status of the message. One of `delivered`, `failed`, or `sent`.
</ResponseField>

<ResponseField name="delivered_at" type="string | null" required>
  ISO 8601 timestamp indicating when the carrier confirmed delivery. `null` if the message has not been delivered (for example, on `sms.sent` or `sms.failed` events).
</ResponseField>

<ResponseField name="sent_at" type="string" required>
  ISO 8601 timestamp indicating when the message was dispatched from Hany's gateway to the carrier.
</ResponseField>

<ResponseField name="timestamp" type="string" required>
  ISO 8601 timestamp indicating when this webhook event was generated and fired by Hany.
</ResponseField>

## Failure Payload Example

When delivery fails, Hany includes additional `error_code` and `error_message` fields to help you understand and respond to the failure:

```json theme={null}
{
  "event": "sms.failed",
  "message_id": "msg_def456uvw",
  "batch_id": "batch_xyz789abc",
  "to": "+15550001234",
  "from": "YourBrand",
  "status": "failed",
  "error_code": "unreachable_subscriber",
  "error_message": "The subscriber is unreachable or has an inactive SIM.",
  "delivered_at": null,
  "sent_at": "2024-06-01T09:00:01Z",
  "timestamp": "2024-06-01T09:05:30Z"
}
```

<ResponseField name="error_code" type="string">
  A machine-readable code describing the reason for failure (for example, `unreachable_subscriber`). Present only on `sms.failed` events.
</ResponseField>

<ResponseField name="error_message" type="string">
  A human-readable description of the failure reason. Present only on `sms.failed` events.
</ResponseField>

## Handling the Webhook in Your Server

The example below shows a minimal Node.js Express handler that receives delivery status events and acknowledges them correctly:

```javascript server.js theme={null}
const express = require('express');
const app = express();
app.use(express.json());

app.post('/webhooks/hany', (req, res) => {
  const payload = req.body;

  // Acknowledge immediately — process the event asynchronously
  res.sendStatus(200);

  // Handle each event type
  if (payload.event === 'sms.delivered') {
    console.log(`[delivered] ${payload.message_id} → ${payload.to} at ${payload.delivered_at}`);
    // Update delivery status in your database, notify your user, etc.
  } else if (payload.event === 'sms.sent') {
    console.log(`[sent] ${payload.message_id} → ${payload.to} dispatched at ${payload.sent_at}`);
    // Mark the message as in-transit in your system
  } else if (payload.event === 'sms.failed') {
    console.error(
      `[failed] ${payload.message_id} → ${payload.to} | ` +
      `${payload.error_code}: ${payload.error_message}`
    );
    // Handle failure: alert your team, queue a retry, notify the recipient, etc.
  } else {
    console.warn(`[unknown] Received unrecognised event type: ${payload.event}`);
  }
});

app.listen(3000);
```

<Tip>
  Always return HTTP `200` immediately and process the event asynchronously. If your handler takes longer than 5 seconds to respond, Hany treats the delivery as failed and begins the retry sequence.
</Tip>

<Note>
  The `batch_id` field lets you correlate individual delivery events back to a bulk campaign. When you send messages via the bulk endpoint, every resulting webhook event for that campaign will carry the same `batch_id`, making it easy to track per-recipient outcomes at scale.
</Note>
