It is 11 p.m., and the dashboard shows a healthy delivery rate for tonight's OTPs. Meanwhile, the support queue fills with "code never arrived." SMS API delivery reports sit right inside that contradiction. Many teams read them as proof. Experienced teams read them as evidence with a confidence level attached.
This guide is for the engineers and product owners who own that gap, especially teams running high-volume OTP SMS delivery. We cover how to read DLR data honestly and how to diagnose missing messages. We also cover webhooks that survive production and how to judge a provider's receipts before signing a contract.
What SMS API delivery reports actually confirm
A delivery report confirms how far a message travelled through the handoff chain, as told by the last hop that answered. That chain runs from our platform to the provider, the operator's SMSC, and finally the handset. "Delivered" usually means the operator or handset acknowledged receipt. It never confirms that a person saw or read the code.
A courier's tracking page is a fair comparison. "Delivered to building" is a useful signal. Nobody would treat it as proof the parcel reached the right desk. The same caution applies to every SMS status field we store.
Reading DLR status codes without fooling ourselves
DLR status codes fall into two tiers, and most production bugs come from mixing them up. Intermediate states include queued, accepted, submitted, and buffered. Final states include delivered, undeliverable, rejected, and expired. A final state should never be overwritten by a later intermediate event.
Normalizing codes across providers
Every vendor uses its own vocabulary. DELIVRD on an SMPP bind, "delivered" in a REST callback, and numeric code 3 elsewhere all describe one outcome. We map each raw value to a small canonical set: queued, submitted, delivered, undeliverable, rejected, expired, and unknown. The raw value stays stored beside it for debugging.
Vendors revise DLR status codes with little notice, so the mapping lives in configuration. A code change then ships as a config push instead of a release.
Inside an SMS delivery receipt: a sample payload
A typical SMS delivery receipt arrives as a JSON POST shaped roughly like this:
{
"message_id": "msg_8f2a",
"to": "+9198XXXXXX12",
"status": "DELIVRD",
"error_code": "000",
"submitted_at": "2026-09-24T10:30:00Z",
"done_at": "2026-09-24T10:30:03Z",
"parts": 1,
"client_ref": "otp-login-5521"
}Three fields do most of the work in practice. The gap between submitted_at and done_at shows route latency. The error code explains failures. The client reference joins the receipt back to a specific login attempt or order, which is what product teams actually care about.
SMS delivered but not received: a diagnostic path
When a receipt says delivered and the customer disagrees, the cause usually sits on the sending side. Working through the checks in a fixed order saves hours of back-and-forth with support and the vendor.
Four checks before escalating
Check latency before status
Pull done_at minus submitted_at for the complaint. An OTP that lands after its validity window is technically delivered and commercially useless. If latency spikes cluster on one operator, the route becomes the prime suspect.
Check the route type
Ask whether the traffic ran over direct operator connectivity or through aggregators. Multi-hop and grey routes sometimes return a success receipt the moment a message leaves an intermediary. The dashboard looks clean while the text never reaches a phone.
Check the handset side
Full inboxes, spam filters, blocked sender IDs, and switched-off phones all cause genuine misses. One complaint rarely proves a route is broken. A cluster of complaints from one operator or circle usually does.
Check compliance filtering
For Indian numbers, a template mismatch or wrong header fails during DLT scrubbing. Those rejections are permanent, so retrying the same content only burns credits. The alert belongs with whoever owns DLT template registration, not the on-call engineer at midnight.
Building an SMS delivery status webhook that holds up in production
A dependable SMS delivery status webhook acknowledges fast, processes asynchronously, and assumes every event can arrive twice or out of order.
Acknowledge fast, process later
The handler verifies the signature, writes the raw payload to a durable queue, and returns a 200 within a few hundred milliseconds. Status mapping, CRM updates, and analytics run in workers. Slow handlers trigger provider retries, and those retries pile more load onto the same slow path during peak OTP traffic.
Duplicates, disorder, and reconciliation
The provider message ID plus the status value makes a dependable idempotency key, so repeat events become no-ops. For ordering, final states outrank intermediate ones, and operator timestamps break ties.
Events still go missing during deploys and certificate expiries. A scheduled job that polls records stuck in a non-final state past the expected delivery window catches whatever the SMS delivery status webhook missed.
Connecting receipts to OTP fallback
Many teams miss this step. When a receipt returns a final failure, or a pending message passes its deadline, the original OTP should be invalidated before any fallback fires. Otherwise a late SMS and a WhatsApp OTP fallback code can both be valid at once. That overlap is a genuine authentication gap.
How to judge a provider's delivery reporting
Buyers should test receipts against reality before trusting any dashboard. Ask how SMS API delivery reports are generated on each route, and whether traffic runs on direct connections with Jio, Airtel, Vi, and BSNL. Request per-operator latency data instead of blended averages. Confirm that failure receipts carry granular error codes.
Then run a seeded test on real handsets across every operator. A consistent gap between reported and actual delivery on any route justifies escalation, with the test logs attached.
Governance, cost, and legacy interoperability
DLR pipelines carry personal data, consume budget, and feed older systems. Each of those areas needs a named owner before launch.
Privacy and access controls
Each receipt links a phone number, a timestamp, and often a customer reference. That bundle is personal data under India's Digital Personal Data Protection Act, 2023, and under GDPR for European recipients. Role-based access control should limit full MSISDNs to a small group, with masking in logs by default.
Retention should follow purpose. Raw payloads for troubleshooting need only a short window, while longer audit trails can keep hashed numbers and final states. BFSI teams should also review the provider's SOC 2 Type II report and data residency terms.
Total cost of ownership
Most providers bundle delivery reporting into the per-message price. The running cost lives in our own stack: webhook ingress, queue throughput, event storage, reconciliation API calls, and seeded test devices. These costs scale with message volume, so they belong in the budget from day one.
The return shows up in two places. Suppressing dead numbers promptly reduces wasted sends. Faster failover, triggered by accurate receipts, lifts OTP completion rates.
Legacy interoperability
Many enterprises still run SMPP binds, on-premises CRMs, and ERPs that expect batch files. The canonical status model bridges them. Workers publish normalized events to Kafka or a similar bus, and adapters feed older systems through nightly CSVs or SOAP calls.
SMPP clients receive each SMS delivery receipt as a deliver_sm PDU, so parsers should read both the message text and optional TLV fields.
Conclusion
SMS API delivery reports earn their value when teams treat them as evidence with known limits. The practical order starts with a canonical status model and an idempotent webhook backed by polling. OTP invalidation tied to receipts comes next, followed by seeded handset tests per operator. Governance and cost tracking belong in the first design review, since retrofitting masking and retention later is expensive. Teams unsure about their current routes can start with a DLR route audit against real handsets.
Frequently asked questions
How do we check SMS delivery status via API when a webhook fails?
Query the provider's status endpoint with the stored message ID. Limit the job to non-final records older than the expected delivery window. Batch requests and respect rate limits so reconciliation never slows live OTP traffic.
What is the difference between a carrier vs handset delivery receipt?
A carrier receipt confirms the operator network accepted the message. A handset receipt confirms the device acknowledged it. Availability varies by country and operator, so carrier-only confirmations deserve lower confidence in SLA reporting.
How can teams spot a fake DLR in SMS traffic?
Warning signs include near-perfect delivery rates, identical latencies across thousands of messages, and almost no error variety. Real networks produce messy data. Seeded tests on real handsets should confirm any suspicion before escalating to the vendor.
How should DLT SMS error codes be handled in India?
Treat them as permanent failures and skip retries. Check the header, template ID, entity ID, and variable tagging against the DLT portal. Route these events to the template owner with the raw error attached.
What does the DELIVRD status mean in an SMPP delivery receipt?
DELIVRD is the final success state in SMPP receipt text, usually shown as stat:DELIVRD. It means the operator reported delivery to the handset. It says nothing about whether the recipient opened or read the message.




