Skip to main content
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)
{
  "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)
{
  "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

event
string
required
The type of webhook event. One of sms.delivered, sms.failed, or sms.sent.
message_id
string
required
The unique identifier of the SMS message. Use this to correlate the webhook event with the original send request.
batch_id
string | null
required
The batch ID if the message was sent via the bulk send endpoint. null for single sends.
to
string
required
The recipient’s phone number in E.164 format.
from
string
required
The sender ID used when the message was sent — either a phone number or an alphanumeric brand name.
status
string
required
The delivery status of the message. One of delivered, failed, or sent.
delivered_at
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).
sent_at
string
required
ISO 8601 timestamp indicating when the message was dispatched from Hany’s gateway to the carrier.
timestamp
string
required
ISO 8601 timestamp indicating when this webhook event was generated and fired by Hany.

Failure Payload Example

When delivery fails, Hany includes additional error_code and error_message fields to help you understand and respond to the failure:
{
  "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"
}
error_code
string
A machine-readable code describing the reason for failure (for example, unreachable_subscriber). Present only on sms.failed events.
error_message
string
A human-readable description of the failure reason. Present only on sms.failed events.

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:
server.js
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);
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.
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.