Mobile SasaDocs

Inbound webhooks

Every MO (mobile-originated) message that matches your keyword or dedicated code is forwarded to your HTTPS endpoint as JSON, as it arrives.

POSThttps://your-app.example.com/inbound (your configured URL)
Payload
{
  "provider": "safaricom",
  "shortcode": "40770",
  "msisdn": "254712345678",
  "message": "JOIN Amina Odhiambo Nakuru",
  "link_id": "8f3a12bc45",
  "received_at": "2026-07-11T10:15:42+03:00"
}

Parameters

FieldTypeRequiredDescription
providerstringNoCarrier the message arrived from (safaricom, airtel, …).
shortcodestringNoThe destination. Your shortcode or long number.
msisdnstringNoThe sender's phone, E.164 without +.
messagestringNoThe full text, keyword included.
link_idstringNoSafaricom LinkID. Needed if you reply through on-demand shortcode flows; absent on other carriers.
received_atstringNoRFC3339 timestamp.

Verify it came from us

Every forward carries X-MobileSasa-Secret and X-MobileSasa-Signature headers so your endpoint can reject forged requests. See Webhook security.

Your response

Return any 2xx quickly. The body is ignored. Replies are sent via the send API or per-keyword auto-replies, not the webhook response.

Design for retries and bursts

  • Treat delivery as at-least-once. Dedupe on msisdn + received_at (or hash the payload).
  • A radio mention can spike hundreds of texts a minute. Queue internally, respond 200 immediately, process async.
  • HTTPS is required; keep the endpoint path unguessable if you don't validate a shared secret.

A minimal receiver

Node.js (Express)
app.post("/inbound", (req, res) => {
  res.sendStatus(200); // ack first, work later

  const { msisdn, message, shortcode } = req.body;
  const [keyword, ...rest] = message.trim().split(/\s+/);

  queue.add("inbound-sms", {
    from: msisdn,
    keyword: keyword.toUpperCase(),
    details: rest.join(" "),
    via: shortcode,
  });
});