Skip to main content
A delivery receipt is how you learn what happened to a message. It is the only way — the send response tells you the message was accepted, nothing more. Ask for one on the send:

What arrives

A POST with Content-Type: application/json:
id is the messageId your send returned. That is how you correlate — there is nothing else. Field names follow Jasmin’s, so a receiver written for Jasmin reads this unchanged. Null fields are omitted rather than sent as nulls, so a receipt for a message that never reached a vendor does not arrive full of empty keys. Every call carries User-Agent: FireFlo/<version>.

level decides whether you are finished

A level 1 receipt is not the outcome. ACCEPTD and BUFFRED both arrive as level 1 and another receipt follows. A receiver that closes its record on the first receipt it sees will report the wrong thing, permanently.
Choose with dlr_level on the send: 1 progress only, 2 the outcome (default), 3 both. Over SMPP the same choice is the registered_delivery octet — bits 1–0 pick none / all / failure-only / success-only, and bit 4 (0x10) adds the intermediate notification. 0x11 is the SMPP spelling of dlr_level: 3.
If you only want to know the final answer, the default is already right and you can ignore level entirely. Switch on it only if you asked for 1 or 3.

stat is the raw state, not a success flag

DELIVRD, UNDELIV, EXPIRED, DELETED, ACCEPTD, UNKNOWN, REJECTD, FAILED, BUFFRED, SEEN. EXPIRED, REJECTD and UNDELIV all bill as failures but mean different things and are worth telling apart — expiry is a handset that was off for a day, rejection is usually a refusal you can act on.

err says why it failed

Sent alongside stat when something reported a failure code, and omitted when nothing did — so a delivered message usually carries no err at all, and its absence means “we were not told” rather than “no error”. It is the same value the SMPP err: field carries for the same failure, so receipts over a webhook and receipts over SMPP agree.
Not every err is a code you can look up. Where your provider has mapped a carrier’s code onto FireFlo’s own set, you get the FireFlo code, and the same number means the same cause across carriers.Where they have not, you get 018 — unknown — which is the default and tells you nothing. Your provider can enable pass-through per carrier, and then you get the carrier’s own number, unchanged instead. That is far more useful, but carriers number failures privately, so 630 from one is unrelated to 630 from another.Treat a value you do not recognise as opaque: log it, and quote it when you raise a query.
err was added after this payload was first published, so receipts you have on file from earlier will not carry it.

The vendor field is absent unless your provider enables it

It names which supplier carries your traffic. Providers withhold that from customers by construction elsewhere, so it is off by default and enabled per deployment. operator_msg_id is not the same disclosure and is always sent: it is the carrier’s own handle for the message, which is what you quote when raising a delivery query. It identifies the message, not the supplier.

Delivery, retries and what counts as success

A 3xx stopped counting as success in 0.11. A redirect to a login page used to be read as delivered and the receipt dropped; it is now retried like any other failure. If you relied on answering 301 or 302, answer 200 instead.A 2xx from a proxy that never reached your application still counts as success — nothing can see past it. If receipts “arrive” but your handler never runs, check what is in front of it.

Three segments, three receipts

By default you get one receipt per submit_sm you sent, each quoting the id you were given. Your provider can collapse that to one with first or last. Every receipt reports the same outcome whichever they choose, because the outbound segments are aggregated before any receipt is built.
A partly-delivered long message is reported FAILED, not partly delivered — resolution waits for the last segment and reports the worst. It is the honest answer, and it makes delivery rates look lower than gateways that report the first segment.

Callbacks not arriving

The success range, the retry budget, and the placeholder trap.

Handling errors

Building for the receipt rather than the response.