Receive webhooks

Goal: receive delivery statuses and inbound replies at your own HTTPS endpoint.
Prerequisites: a publicly reachable HTTPS URL that can accept POST requests.

Sending is asynchronous: POST /messages returns immediately, and the delivery lifecycle (sent → delivered, or failed) is reported to your endpoint afterward as a JSON POST. Campaigns report their status the same way.

Alaris documents a different flow: the vendor calls Alaris with GET/POST query parameters (command=deliver, dlvrMsgId, dlvrMsgStat, username, password). That is inbound to the platform, not this client webhook.

Steps

1. Decide where events go

The platform picks the first URL that is set, in this order:

  1. The webhook field in the send request body.
  2. The webhookUrl configured on the Sender ID.
  3. The webhook configured on the API key.

If none is set, the event is dropped — your only recourse is polling GET /edrs.

2. Point a send at your endpoint

curl -X POST https://client.routeon.io/api/v1/messages 
  -H "X-API-Key: <API_KEY>" -H "Content-Type: application/json" 
  -d '{
    "contact": "905063565285",
    "channel": 1,
    "senderId": "Routeon",
    "payload": { "text": "Hello from Routeon" },
    "webhook": "https://your.app/hooks/routeon"
  }'

3. Handle the event types

messageStatus — delivery lifecycle:

{
  "event": "messageStatus",
  "message": {
    "messageId": "4aab1947-7a34-4efe-8c51-e9c8f9b1412b",
    "omniTransactionId": "57f29fdd-1127-45b3-a7d5-260a831a4662",
    "channel": 1,
    "senderId": "Routeon",
    "status": "delivered",
    "timestamp": "2023-09-08T15:30:00Z",
    "contact": "905324546496",
    "cost": "1.00"
  },
  "reason": { "code": 0, "text": "" }
}

message — inbound (mobile-originated) reply from the recipient:

{
  "event": "message",
  "message": {
    "messageId": "4aab1947-7a34-4efe-8c51-e9c8f9b1412b",
    "omniTransactionId": "57f29fdd-1127-45b3-a7d5-260a831a4662",
    "timestamp": "2023-09-08T15:30:00Z",
    "channel": "SMS",
    "senderId": "2813380676",
    "contact": "8172958708",
    "payload": { "text": "test SMS" }
  }
}

Note: channel is an integer in messageStatus (e.g. 1) but a string in message (e.g. "SMS"). Branch on event first.

Campaign status — for broadcasts, one event per state change:

{ "status": "completed", "webhook": "https://your.app/hooks/routeon", "broadcastId": 12345, "message": null }

4. Minimal receiver

import express from "express";

const app = express();
app.use(express.json());

app.post("/hooks/routeon", (req, res) => {
  const body = req.body;
  // Acknowledge fast; do heavy work asynchronously.
  res.status(200).end();

  if (body.event === "messageStatus") {
    // dedupe on body.message.messageId, then update your records
  } else if (body.event === "message") {
    // inbound reply — body.message.payload.text
  } else if (body.broadcastId) {
    // campaign status — body.status
  }
});

app.listen(8080);

Verify

Send a test message with webhook pointed at your endpoint and confirm you receive a messageStatus event with a delivered (or failed) status carrying the same messageId you got back from the send.

Delivery semantics

  • No signature header. Requests are plain HTTPS POSTs. Protect the endpoint by using an unguessable URL path, serving it over HTTPS, and — if possible — restricting inbound traffic to the platform.
  • Respond 2xx quickly (well under 30 seconds). Queue heavy processing behind a fast acknowledgement.
  • Treat delivery as at-least-once. Retries can produce duplicates — deduplicate on messageId (or broadcastId for campaigns).
  • If an event is missed, reconcile with GET /edrs?messageIds=....

Common failures

Symptom Cause Fix
No events arrive No webhook resolved Set webhook in the request, on the Sender, or on the key
Duplicate events At-least-once retries Dedupe on messageId / broadcastId
Events stop under load Slow receiver / timeouts Acknowledge 200 immediately, process async
channel parsing errors Type differs by event Branch on event; channel is int in messageStatus, string in message