Quick answer: which SMS API has reliable webhooks?
The most reliable SMS API for webhooks is the one that gives your team clear status callbacks for the countries, sender identities, templates, and message types you actually send. Do not choose from a global claim alone. Test accepted, sent, delivered, failed, expired, and unknown states in each country where you plan to send production SMS, then check whether retries, duplicate callbacks, and support evidence are easy to trace.
For deliveries in Spain, the review should still be country-specific rather than Spain-only. Send controlled traffic to Spanish test numbers, compare callback timing and final-state coverage, and keep the same checklist for the UK, Germany, India, Hong Kong, or any other country where OTPs and account alerts matter.
| Reliable webhook signal | What to verify | Why buyers should care |
|---|---|---|
| Clear lifecycle states | Accepted, sent, delivered, undelivered, failed, expired, rejected, and unknown map into your own status model. | Support can explain what happened without guessing from one send response. |
| Country-level callback behavior | Spain and every other country where you send production SMS can be filtered by country, route, sender identity, provider, and template. | A global average will not show one-country delivery trouble quickly enough. |
| Retry-safe ingestion | Duplicate callbacks, provider retries, and out-of-order events do not change final states incorrectly. | Webhook reliability includes your handler, not only the provider callback. |
| Support-ready evidence | Provider message IDs, raw payloads, error categories, and timestamps stay attached to the message timeline. | A real incident can be escalated with evidence instead of screenshots and assumptions. |
The API response is not the delivery status
When your send request succeeds, you usually know one thing: the messaging platform accepted your request. Delivery is asynchronous. The carrier may accept, delay, filter, expire, reject, or report a status later. Your application needs SMS delivery tracking webhooks because the truth changes after the initial API response.
Twilio describes outbound status callbacks as a way to track status changes through the message lifecycle. Vonage notes that a successful SMS API response means a message was queued, not necessarily delivered, and that delivery receipts vary in reliability by market and receipt type.
If your database only has sent = true, your support team has no delivery tracking. It has a guess.
That same event stream can power dashboards for OTP success rates, delayed messages, fallback usage, country-level delivery tracking, and support timelines, as long as each callback is stored idempotently and tied back to the original send request.
Design the send side first
Delivery tracking is easier when the original transactional SMS request already carries a use case, template key, message metadata, idempotency key, and stable message ID.
Read the transactional SMS API guideSMS delivery tracking webhooks vs inbound SMS webhooks
SMS webhook searches often mix three related ideas: delivery tracking webhooks, inbound SMS receive webhooks, and OTP verification events. They should not all update the same field. A delivery callback explains what happened after your app sent a message. An inbound webhook captures a user reply. A verification event records whether the user completed, retried, expired, or abandoned an OTP flow.
| Webhook type | What it tells you | Where to store it |
|---|---|---|
| Delivery status callback | The provider or carrier changed the outbound message status, such as sent, delivered, failed, expired, or unknown. | Message delivery timeline. |
| Inbound receive webhook | A recipient replied to a number, short code, or sender that can receive messages. | Conversation, reply, opt-out, or support intake record. |
| OTP verification event | A user requested, retried, checked, completed, failed, or timed out a verification attempt. | Verification attempt timeline. |
| Retry event | Your platform or provider retried delivery, resent an OTP, or reprocessed a webhook callback. | Retry log linked to the original message or attempt. |
Keeping those timelines separate makes dashboards easier to trust. Support can see whether an OTP failed because the SMS was not delivered, because the user entered the wrong code, because a fallback path was used, or because an inbound reply belongs to a different workflow entirely.
Compare status callbacks by country, not just provider
A provider can look reliable in one country and still produce delayed, missing, or less detailed delivery signals somewhere else. If you are choosing an SMS API for deliveries in Spain, Germany, the UK, India, Hong Kong, or any other country where you plan to send production SMS, test the status callback behavior by destination country before relying on a global dashboard average.
| Review item | What to check | Why it matters |
|---|---|---|
| Country test sends | Send OTPs and account alerts to real test numbers in each country where you plan to send production SMS, including Spain if Spanish delivery is important. | Local carrier routes can expose different callback timing, status vocabulary, and final-state coverage. |
| Country filters | Confirm the dashboard can filter callbacks by destination country, template, provider, route, and sender identity. | A global delivery chart can hide a country-specific issue until support tickets pile up. |
| Callback latency | Measure time from API acceptance to sent, delivered, failed, expired, or unknown status. | OTP and account-alert flows need fallback decisions before the user gives up. |
| Final-state coverage | Check whether delivered, undelivered, rejected, expired, and unknown are reported consistently by country. | A provider that reports clear states in one country may return less useful delivery evidence in another. |
| Retry evidence | Check whether webhook retries keep the same provider message ID, event ID, and callback attempt details. | Support needs to know whether the message failed, the callback failed, or the event was duplicated. |
The safest comparison is practical: pick two or three target countries, send the same message types, capture every callback, and review whether support can explain the result without opening several provider screens.
If Spain is the current search target or production SMS country, make it one row in the same scorecard instead of a separate exception. The same review should show callback latency, final-state coverage, retry behavior, sender identity, and failure reasons for every country your product depends on.
What makes SMS webhooks and status callbacks reliable?
If you are comparing SMS APIs for reliable webhooks and status callbacks, do not judge reliability by the send response alone. Test how the provider reports accepted, sent, delivered, failed, expired, and unknown states, how quickly callbacks usually arrive, and whether retries or duplicate callbacks can be tied back to the original message ID.
Delivery receipts are provider and carrier signals, not proof that a human read the message. The practical goal is visibility: your product and support teams should know whether a message was queued, handed off, reported delivered, failed, delayed, or still missing a final state.
| Question | Why it matters for delivery | What to store |
|---|---|---|
| Which delivery states are exposed? | A useful callback feed should distinguish accepted, sent, delivered, failed, expired, rejected, and unknown states. | Normalized status, raw provider status, timestamp, and final-state flag. |
| How are retries reported? | Retries can create duplicate or delayed events, especially when providers retry webhook delivery after an endpoint error. | Idempotency key, provider message ID, retry count, and callback attempt timestamp. |
| How fast do callbacks arrive? | Status latency affects OTP fallbacks, support timelines, and dashboards for time-sensitive account alerts. | Send time, callback received time, provider event time, and callback latency. |
| Can dashboards filter by country and route? | Country-level views help you spot country-specific delivery changes without mixing them into global traffic. | Destination country, sender identity, template, route, provider, and final outcome. |
| What happens when no final receipt arrives? | Some messages may stay unknown, so the product needs a safe fallback and support explanation. | Unknown state, timeout window, fallback offered, and user outcome. |
A strong delivery dashboard should separate message delivery from user completion and still let teams filter by country. For OTPs, track whether the user verified after the message was sent. For account alerts, track whether the alert record has enough evidence for support to explain what happened without overclaiming that the recipient saw the SMS.
Know what each SMS status means
Use the sent, accepted, delivered, failed, expired, and unknown definitions before designing callback dashboards or support replies.
Read the SMS status guideNormalize provider events into your own model
Every provider has its own payload names, status vocabulary, retry behavior, and error codes. Store the raw payload, but do not make the rest of your product depend on raw provider fields. Normalize events into a compact lifecycle that your app understands.
| Normalized status | Typical provider meaning | Final? |
|---|---|---|
| accepted | API request accepted or message queued. | No |
| sent | Message handed to the downstream network or messaging channel. | No |
| delivered | Provider or carrier received a successful delivery receipt. | Usually |
| undelivered | Delivery receipt says the handset or destination was not reached. | Yes |
| failed | Provider could not send, route, or process the message. | Yes |
| expired | Carrier retry window ended before delivery. | Yes |
| rejected | Carrier, provider, or policy rejected the message. | Yes |
| unknown | No useful final state is available. | Maybe |
Do not assume events arrive exactly once or in perfect order. Webhook systems retry, networks fail, and providers can add fields over time. Design your event processor to be idempotent and tolerant of extra payload data.
The records worth storing
You need two levels of storage: the current message summary for quick reads, and an append-only event timeline for audit and debugging. The summary powers dashboards and product state. The timeline explains how the message got there.
| Field | Where | Why |
|---|---|---|
| message_id | messages | Stable internal ID used by your app. |
| provider_message_id | messages | Lets you reconcile with provider logs and support. |
| recipient_hash | messages | Useful for debugging without exposing phone numbers broadly. |
| destination_country | messages | Delivery behavior and rules vary heavily by country. |
| sender_identity | messages | Separates 10DLC, toll-free, short code, sender ID, or route behavior. |
| template_key | messages | Helps detect template-specific filtering or copy problems. |
| current_status | messages | Fast product reads and support filtering. |
| event_id | message_events | Webhook deduplication when the provider supplies a unique ID. |
| status | message_events | The lifecycle state from each callback. |
| error_code | message_events | Debugging, alerting, and provider support escalation. |
| raw_payload | message_events | Future-proof audit trail when mappings change. |
| received_at | message_events | Your system time, separate from provider event time. |
create table sms_messages (
id text primary key,
provider_message_id text,
recipient_hash text not null,
destination_country text,
sender_identity text,
template_key text,
current_status text not null,
created_at timestamptz not null,
updated_at timestamptz not null
);
create table sms_message_events (
id text primary key,
message_id text not null references sms_messages(id),
provider_event_id text,
status text not null,
error_code text,
provider_occurred_at timestamptz,
raw_payload jsonb not null,
received_at timestamptz not null
);Build the webhook handler like an ingestion pipeline
Twilio's webhook security docs recommend HTTPS and signature validation, and warn that webhook parameters can evolve. That is the right shape for any provider integration: validate authenticity, preserve the raw request, map only the fields you understand, and do not break when new fields appear.
- Receive the raw request body before any middleware mutates it.
- Verify the provider signature using the exact URL, headers, and raw body or form parameters required by that provider.
- Reject invalid signatures before writing state.
- Map the provider message ID and status into your normalized lifecycle.
- Deduplicate by provider event ID when available, otherwise by provider message ID, status, and provider timestamp.
- Write the event and update the current summary in one transaction.
- Return 2xx only after the event is safely stored.
- Send unknown statuses to a dead-letter or review queue instead of dropping them.
async function handleSmsWebhook(request: Request) {
const rawBody = await request.text();
const signature = request.headers.get("x-provider-signature");
if (!verifyWebhookSignature({ rawBody, signature, url: request.url })) {
return new Response("invalid signature", { status: 401 });
}
const event = parseProviderPayload(rawBody);
const normalized = normalizeSmsEvent(event);
await db.transaction(async (tx) => {
await tx.smsMessageEvents.upsert({
providerEventId: normalized.providerEventId,
messageId: normalized.messageId,
status: normalized.status,
errorCode: normalized.errorCode,
rawPayload: event
});
await tx.smsMessages.updateCurrentStatus(normalized.messageId, normalized.status);
});
return new Response("ok", { status: 200 });
}Handle duplicates and out-of-order events
Webhook delivery is usually at-least-once. That means duplicate callbacks are normal. If your handler increments counters, sends user notifications, or triggers fallbacks on every callback without deduplication, one provider retry can become a product bug.
Out-of-order events are just as important. You might receive accepted after delivered, or a delayed intermediate state after a final state. Keep an explicit status precedence model so old intermediate events do not downgrade a message that already reached a final state.
| Problem | Bad behavior | Better behavior |
|---|---|---|
| Duplicate delivered event | Send the user two success notifications. | Upsert by event ID and make side effects idempotent. |
| Late sent event after failed | Change the message back to in progress. | Store the event but keep the final summary state. |
| Unknown provider status | Throw 500 forever or drop it silently. | Store raw payload, mark unknown, and alert engineering. |
| Provider outage | Lose callbacks during downtime. | Return non-2xx only when storage failed and rely on provider retries plus reconciliation jobs. |
Give support a real delivery timeline
The best delivery tracking work shows up in support. A support agent should be able to answer: when did the user request the SMS, what number was used, which sender identity sent it, what did the provider say, what did the carrier say, did we retry, and what should the user do next?
- Show timestamps in the user's local timezone and UTC.
- Mask phone numbers by default, with audited reveal access for trusted support roles.
- Translate provider errors into plain-language support notes.
- Link every message to the product action that triggered it.
- Expose country, sender identity, provider, and template so patterns are visible.
- Make it easy to copy a provider message ID for escalation.
This is also where product analytics becomes useful. If OTP completion drops in one country, your webhook data can show whether users are failing to request codes, carriers are rejecting messages, or receipts are delayed.
Turn webhook evidence into support actions
When a message fails or stays unknown, support needs a clear runbook for error codes, country patterns, and the next safe user action.
Read the SMS error-code runbookUse tracking to improve OTP completion
For verification flows, connect webhook events to resend timing, abuse checks, fallback choices, and the final verification outcome so OTP reliability is measured by completed logins, not accepted sends.
Read the OTP delivery guideSMS webhook FAQ
FAQ
Which SMS API has reliable webhooks and status callbacks?
Choose an SMS API by testing its callback lifecycle by country, not by the send response alone. Look for clear accepted, sent, delivered, failed, expired, and unknown states; idempotent retry handling; provider message IDs; country filters; callback latency; and raw payload storage for support review.
Should webhook handlers return 200 immediately?
Return 2xx only after you have validated and durably stored the event. If storage fails, a non-2xx response lets the sender retry instead of losing the event.
Do I need to store raw webhook payloads?
Yes. Store them with access controls. Raw payloads help when provider fields change, mappings are wrong, support escalates, or you need to reconcile with provider logs.
Can I trust delivered as a final truth?
Use delivered as the best available delivery signal, but avoid wording that guarantees a human saw the message. Providers document cases where delivery receipt certainty varies.
Should I poll instead of using webhooks?
Polling can be useful for reconciliation, but webhooks should be the primary path for timely delivery updates. A nightly reconciliation job can catch missed or inconsistent events.
How should I compare SMS API webhooks for a country like Spain?
Use the country as a test case, not the whole positioning. Send real test traffic for every country your product depends on, such as Spain, Germany, the UK, India, or Hong Kong. Confirm that delivered, failed, expired, and unknown states arrive clearly, verify country-level filtering in your webhook dashboard, and test retry behavior when your endpoint is temporarily down.
How do I track SMS delivery rates by country?
Store the destination country on every outbound message and join it against webhook status events. Group by country and status to surface per-country delivery rates, failure reasons, and latency. This lets you detect carrier-level filtering or route degradation in specific countries like Spain, Germany, or the UK before users report problems.
Compare SMS pricing by country
Review per-message rates for the United States, United Kingdom, Canada, and every other supported destination before choosing an SMS API provider.
See country pricing