Skip to main content

Delivery Reports

A delivery report (DLR) is the asynchronous, per-message acknowledgement that tells you the final outcome of an SMS — whether it was delivered to the handset, rejected by the operator, expired, or failed. Without a DLR, all you know is that the platform accepted the message for sending.

How to Receive DLRs

DLRs are pushed to an HTTPS webhook you control as soon as the operator reports the final state. Enable them by setting enableDelivery: true on your send request and configuring the destination URL in the wholesale portal under Settings → Webhooks.

Typical end-to-end latency from submission to a DELIVERED callback is a few seconds.

Webhook Payload

DLRs are delivered via HTTP POST to the URL you configure. The payload identifies the message and gives the new status.

{
"id": "msg_abc123",
"globalId": "req_1234567890",
"destination": "+393351234567",
"status": "DELIVERED",
"statusCode": 0,
"submitDate": "2026-05-14T10:23:11.000+0200",
"doneDate": "2026-05-14T10:23:14.221+0200",
"operator": "TIM",
"parts": 1
}

Field reference

FieldMeaning
idPer-message identifier returned by the send response
globalIdCorrelation ID for the original submission
destinationThe recipient phone number
statusFinal state (see table below)
statusCodeNumeric code matching the status
submitDateWhen the platform accepted the message
doneDateWhen the final state was reached
operatorReceiving mobile operator (best effort)
partsNumber of SMS parts billed

DLR Status Codes

StatusCodeMeaningBillable?
DELIVERED0The handset confirmed receptionYes
BUFFERED1Stored on the operator, not yet deliveredYes
EXPIRED2Validity period elapsed before deliveryYes
REJECTED3The operator rejected the messageNo
UNDELIVERABLE4Wrong number, blacklisted, opted-outNo
UNKNOWN5No final state received within the validity windowDepends
FAILED6Internal failure before submission to operatorNo

Billing rules can vary by operator; the wholesale portal is the source of truth for your account.

Webhook Requirements

  1. HTTPS only — Plain HTTP is not accepted.
  2. Respond with 200 OK within 10 seconds — Otherwise the DLR is retried.
  3. Idempotency — The same DLR may be retried on transient errors. Use id as the dedup key.
  4. Order is not guaranteed — A BUFFERED event may arrive after a DELIVERED. Use doneDate to reconcile.
  5. Retry policy — Failed deliveries are retried with exponential back-off for up to 24 hours, then dropped.

Example Webhook Handler

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.post("/webhook/dlr")
def dlr():
payload = request.get_json(force=True)
msg_id = payload["id"]
status = payload["status"]
# Persist; downstream processing should be async.
save_dlr(msg_id, status, payload)
return jsonify({"ok": True}), 200

Next Steps