Key Takeaways
- HTTP 200 on POST /messages means the control plane accepted the job. Delivery is a later webhook (or DLR poll).
- Verify X-SmsGateway-Signature as v1=HMAC-SHA256("{timestamp}.{body}", secret) and reject stale timestamps.
- Deduplicate on X-SmsGateway-Event-Id. Retries are normal; applying Delivered twice must be a no-op.
- Subscribe to message.delivered, message.failed, and message.received. Do not infer inbound from DLR.
- You bring the Android phone and operator credit. Gateway pricing is devices plus send volume.
An android sms gateway api webhook is how your backend learns what the radio did after you hung up the HTTP send. Without it you either poll GET /api/v1/messages/{id} in a tight loop or you lie to users (“sent!”) on accept. This long-tail is the webhook design guide. Live paths stay in the Developer Center; the send contract is covered in the Android SMS Gateway API cornerstone.
You still pair an Android phone and fund the SIM. We meter devices and send volume — not aggregator per-segment fees.
Event bus (illustrative)
Phone radio → control plane → POST /api/v1/webhooks consumer
Why polling DLR is not enough
Polling works for a demo. In production you have bursts, backoff, and a fleet. Webhooks invert the relationship: the control plane tells you when status changes, including inbound SMS that polling a send-id will never see.
Treat accept, sent, and delivered as three clocks. If your user-facing copy collapses them into one, you will page yourself for a carrier that never returns DLR.
Some networks leave status at Sent. That can be terminal. Design UI copy for “handed to the operator” versus “handset acknowledged.” The FAQ on DLR in product docs is the same distinction.
Event catalog
| type | When it fires | What you should persist | Typical next action |
|---|---|---|---|
message.delivered | Operator/handset DLR says delivered | message id, deliveredAt, deviceId | Mark notification delivered; stop retry |
message.failed | Radio or operator failure | id, failure, deviceId | Retry on another device or fail the OTP challenge |
message.received | Inbound SMS on the SIM | from, body, receivedAt, deviceId | STOP list, ticket, or two-way reply via POST /messages |
Register endpoints with POST https://app.sms-gateway.app/api/v1/webhooks. Field-level schema is owned by the OpenAPI, not this article.
Phone → control plane → your HTTPS
The Android app is what talks to the modem. It reports status upstream. Your server never opens a socket to the phone. If the phone is offline, events queue until it reconnects — inbound SMS included, subject to the device remaining powered and the app un-killed. That is why a desk phone on a charger still matters for two-way.
Illustrative delivery payload:
{
"id": "evt_01K2F8QW3N4RXB7M",
"type": "message.delivered",
"createdAt": "2026-08-12T14:04:09Z",
"apiVersion": "2026-08-12",
"data": {
"message": {
"id": 41823,
"number": "+14155552671",
"text": "Your verification code is 481920",
"status": "Delivered",
"campaignId": 17,
"deviceId": 3,
"metadata": { "orderId": "1234" },
"sentAt": "2026-08-12T14:04:02Z",
"deliveredAt": "2026-08-12T14:04:09Z"
}
}
}Inbound shape:
{
"id": "evt_01K2F8QW3N4RXB7M",
"type": "message.received",
"createdAt": "2026-08-12T14:04:09Z",
"apiVersion": "2026-08-12",
"data": {
"message": {
"id": 41822,
"number": "+14155552671",
"text": "Yes, please reschedule to Thursday.",
"status": "Received",
"deviceId": 3,
"receivedAt": "2026-08-12T14:04:09Z"
}
}
}HMAC, timestamp, and replay
Header contract: X-SmsGateway-Signature = v1=hex(HMAC-SHA256("{timestamp}.{body}", secret)) with X-SmsGateway-Timestamp in unix seconds. Hash the raw body, not a re-serialized JSON object. Whitespace changes break signatures.
Reject timestamps older than about 300 seconds to limit replays. Still persist event ids — a retry inside the window is legitimate. Language samples for verification live as HTTPS/JSON snippets, not as a packaged “Complete SDK.”
PHP-oriented samples: PHP HTTPS examples. C# samples: C# HTTPS examples.
Idempotency and ordering
Deduplicate on X-SmsGateway-Event-Id. Do not assume delivered arrives after failed, or that inbound related to a send shares an id you issued. Store gateway message id as a foreign key on your notification row.
At-least-once delivery means your handler must be safe to run twice. “Increment SMS used” on every webhook is how people double-count. Key the increment on event id.
Inbound SMS and STOP
message.received is how STOP lands if the user texts the SIM. Honor it for campaigns. OTP policy is yours: many teams keep authentication working after a marketing opt-out. Wire keywords through auto-reply and STOP while the device is online, and still persist the webhook for your system of record.
Two-way product context: two-way SMS inbox.
Your endpoint failures
Return 2xx only after you have durably stored the event (or idempotently recognized it). 5xx and timeouts invite retries. Do not do slow vendor calls (Slack, your ESP) inline; enqueue. If you 401 on a bad signature, investigate clock skew before rotating secrets in a panic.
Keep the URL on HTTPS. Pin to a dedicated path. Do not reuse a public debug catcher in production.
OTP: delivered vs guessed
Do not mark an OTP “delivered” on send accept. Wait for message.delivered or a timeout that triggers a second device. If DLR never comes, decide in policy whether to offer voice or a second channel — do not resend the same digits forever.
Isolate OTP devices so bulk cannot delay the webhook that unblocks login.
Ops and cost
Alert on webhook error rate, signature failures, and lag from sentAt to event createdAt. Lag is often the phone (doze, OEM killer), not your ELB.
Webhooks are free of extra SKU drama; sends still consume operator airtime and plan volume. Free: 300 SMS lifetime. Developer: 25,000 SMS per year. Starter / Professional / Business uncap platform send volume with device caps 2 / 5 / 15. Pause on Free and Developer when the allowance is exhausted.
Checklist
- Endpoint registered; secret in a vault; old secrets overlapping during rotation.
- Signature + timestamp verified on the raw body.
- Event id uniqueness constraint in the database.
- Handlers for delivered, failed, received — even if received only logs at first.
- Staff canary: send → webhook row appears within your SLO before customer OTP.
Next steps
Stand up a staging URL, send one SMS to yourself, and prove the three clocks in your own logs. Then add signature checks. Architecture around the control plane: gateway server. App install: downloads.
Related product pages
Jump to the live product docs for this topic—not another long-form article.
- SMS webhook integrationInbound and status events
- SMS API documentationLive endpoint reference
- device and SMS volume pricingPlans and allowances
- Android SMS gateway product guideDefinition, product, and how to buy




