UC-004 — Check Delivery Status
| Field | Value |
|---|---|
| ID | UC-004 |
| Goal | Check message delivery status with polling and webhooks |
| Channel | Multi-channel (SMS, RCS, WhatsApp) |
| Complexity | Basic |
| Estimated time | 15 minutes |
| APIs involved | GET /api/partner-gateway/v1/messages/status/{customerMessageId}, GET /api/partner-gateway/v1/messages/status |
Real-world scenarios
- OTP dashboard — Send verification: BancaSicura monitors in real time whether OTP codes are delivered within 5 seconds, activating an alternative channel in case of failure.
- Campaign delivery report: MarketingPro generates an end-of-campaign SMS report with delivery percentages, errors, and average times.
- Real-time monitoring: TechStore integrates the status check into their CRM to show a "Delivered" or "Pending" badge next to each sent communication.
Prerequisites
Before you begin, make sure you have:
- Active API Key → How to get one
- Sufficient credit → Check in the Qlara Dashboard
- A previously sent message with its
messageId
Add "simulation": true in the request body to validate the flow without actually sending messages and without consuming credit.
Polling vs Webhook
| Feature | Polling | Webhook |
|---|---|---|
| Direction | Your server calls the API | The API calls your server |
| Latency | Depends on polling interval | Real time |
| API load | Proportional to the number of calls | One call per event |
| Complexity | Low (just a loop) | Medium (requires a public endpoint) |
| Ideal for | Spot checks, debug, small volumes | Production, high volumes, real-time |
Use polling for tests, debugging, and manual checks. Use webhooks in production to receive real-time notifications without overloading the API. See the Webhook guide for configuration.
Step 1 — Send a message (prerequisite)
To check the status, you must first have sent a message and saved the messageId. Quick example with SMS:
curl -s -X POST https://api.qlara.ai/api/message-server/sms/send \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-d '{
"destination": "+393471234567",
"sender": "BancaSicura",
"body": "Il tuo codice OTP e: 847293. Valido per 5 minuti.",
"enableNotification": true
}'
Response
{
"messageId": "d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890",
"simulation": false,
"results": {
"sms": {
"accepted": true,
"unicode": false,
"parts": 1,
"reasons": []
}
}
}
Behind the scenes — Status timeline
After sending, the message goes through these states in sequence:
- In flight — The gateway accepted the message and forwarded it, but no receipt has come back yet. The status reads
ACCEPTEDon SMS andUNKNOWNon RCS and WhatsApp, which have no intermediate status of their own. - Final status — The carrier or platform receipt arrives and the status no longer changes:
- SMS:
DELIVERED, or a failure:REJECTED,UNDELIVERABLE,EXPIRED(not delivered within its validity window) orDELETED. - RCS:
DELIVERED, or a failure:ERROR,DISABLED,UNSUPPORTEDorEXPIRED. - WhatsApp: the same as RCS, plus
CONVERSATION_CLOSED.
- SMS:
- Read (RCS/WhatsApp only) — Reading is not a status: the message stays
DELIVERED. On WhatsApp the read time is set inreadDate; on RCS and WhatsApp the webhook also sends aREADevent.
The transition from ACCEPTED/UNKNOWN to DELIVERED typically takes 1-5 seconds for SMS, 0.5-2 seconds for RCS/WhatsApp.
Step 2 — Single polling
Query the status of a single message using the messageId as a path parameter and the channel as a query parameter.
curl -X GET "https://api.qlara.ai/api/partner-gateway/v1/messages/status/d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890?channel=SMS" \
-H "X-Api-Key: YOUR_API_KEY"
Response — DELIVERED
{
"customerMessageId": "d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890",
"channel": "SMS",
"destination": "+393471234567",
"deliveryStatus": "DELIVERED",
"deliveryStatusDescription": "delivered",
"sendDate": "2026-04-09T15:00:00+02:00",
"deliveryDate": "2026-04-09T15:00:03+02:00",
"readDate": null
}
Response — UNDELIVERABLE
{
"customerMessageId": "d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890",
"channel": "SMS",
"destination": "+393471234567",
"deliveryStatus": "UNDELIVERABLE",
"deliveryStatusDescription": "undeliverable",
"sendDate": "2026-04-09T15:00:00+02:00",
"deliveryDate": null,
"readDate": null
}
Behind the scenes — Complete status table
| Status | Webhook code | deliveryStatusDescription | Channel | Description |
|---|---|---|---|---|
ACCEPTED | 1 | accepted | SMS | Handed to the carrier, no receipt yet. Not final |
REJECTED | 2 | rejected | SMS | The carrier refused the message. Final |
DELIVERED | 3 | delivered | SMS, RCS, WhatsApp | Message delivered to the handset. Final |
EXPIRED | 4 | expired | SMS, RCS, WhatsApp | Not delivered within its validity window. Final |
DELETED | 5 | deleted | SMS | Cancelled before delivery. Final |
UNDELIVERABLE | 6 | undeliverable | SMS | The destination cannot receive it (unreachable or invalid number). Final |
ERROR | 9 | general error | RCS, WhatsApp | Delivery failed. Final |
DISABLED | 10 | disabled | RCS, WhatsApp | The recipient has the channel switched off. Final |
UNSUPPORTED | 11 | unsupported | RCS, WhatsApp | The device or number does not support the channel. Final |
CONVERSATION_CLOSED | 12 | conversation closed | The 24-hour customer-service window had closed. Final | |
UNKNOWN | 0 | unknown | SMS, RCS, WhatsApp | No status held for the message. On RCS and WhatsApp, the normal state while in flight |
Note on webhook codes: The delivery-status webhook sends the same numeric codes as this table in statusCode, and the lowercase description in description. In the status polling API, the deliveryStatus field is the name in the first column. The webhook fires when the carrier or platform receipt arrives, so it never sends ACCEPTED (1) or UNKNOWN (0).
Step 3 — Batch polling
To check the status of multiple messages in a single call, use the batch endpoint. All IDs must belong to the same channel.
curl -X GET "https://api.qlara.ai/api/partner-gateway/v1/messages/status?channel=SMS&ids=d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890,a1b2c3d4-e5f6-7890-abcd-ef1234567890,f47ac10b-58cc-4372-a567-0e02b2c3d479" \
-H "X-Api-Key: YOUR_API_KEY"
Response — Batch status
[
{
"customerMessageId": "d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890",
"channel": "SMS",
"destination": "+393471234567",
"deliveryStatus": "DELIVERED",
"deliveryStatusDescription": "delivered",
"sendDate": "2026-04-09T15:00:00+02:00",
"deliveryDate": "2026-04-09T15:00:03+02:00",
"readDate": null
},
{
"customerMessageId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"channel": "SMS",
"destination": "+393481234567",
"deliveryStatus": "DELIVERED",
"deliveryStatusDescription": "delivered",
"sendDate": "2026-04-09T15:00:01+02:00",
"deliveryDate": "2026-04-09T15:00:04+02:00",
"readDate": null
},
{
"customerMessageId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"channel": "SMS",
"destination": "+393491234567",
"deliveryStatus": "UNDELIVERABLE",
"deliveryStatusDescription": "undeliverable",
"sendDate": "2026-04-09T15:00:01+02:00",
"deliveryDate": null,
"readDate": null
}
]
IDs that do not match any message are silently omitted from the response. If you send 3 IDs and receive only 2 results, the third was not found.
Behind the scenes — Batch limits and best practices
- ID limit: You can send up to several hundred IDs in a single call. For higher volumes, split into multiple requests.
- Same channel: All IDs must belong to the same channel (SMS, RCS, or WHATSAPP). For mixed channels, make separate calls.
- Result order: Results are not ordered. Use
customerMessageIdto match with your records. - Polling interval: For automated checks, use an interval of at least 5 seconds between calls. For high volumes, switch to webhooks.
Example: polling loop with retry
A bash script that checks the status of an SMS every 3 seconds, with a 30-second timeout. It stops on DELIVERED or on a final SMS failure; the failure statuses depend on the channel, so for RCS use ERROR|DISABLED|UNSUPPORTED|EXPIRED and for WhatsApp add CONVERSATION_CLOSED:
#!/bin/bash
MESSAGE_ID="d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890"
API_KEY="YOUR_API_KEY"
MAX_ATTEMPTS=10
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
ATTEMPT=$((ATTEMPT + 1))
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS..."
STATUS=$(curl -s -X GET \
"https://api.qlara.ai/api/partner-gateway/v1/messages/status/${MESSAGE_ID}?channel=SMS" \
-H "X-Api-Key: ${API_KEY}" | jq -r '.deliveryStatus')
echo "Status: $STATUS"
case "$STATUS" in
DELIVERED)
echo "Message delivered!"
exit 0
;;
REJECTED|UNDELIVERABLE|EXPIRED|DELETED) # final SMS failures
echo "Delivery error: $STATUS"
exit 1
;;
esac
sleep 3
done
echo "Timeout: final status not reached"
exit 2
Channel comparison
| Field | SMS | RCS | |
|---|---|---|---|
deliveryStatus | ACCEPTED, REJECTED, DELIVERED, EXPIRED, DELETED, UNDELIVERABLE, UNKNOWN | DELIVERED, EXPIRED, ERROR, DISABLED, UNSUPPORTED, UNKNOWN | DELIVERED, EXPIRED, ERROR, DISABLED, UNSUPPORTED, CONVERSATION_CLOSED, UNKNOWN |
| In-flight status | ACCEPTED | UNKNOWN | UNKNOWN |
readDate | Always null | Always null | Present if read |
| Typical DELIVERED latency | 1-5 seconds | 0.5-2 seconds | 0.5-2 seconds |
| READ webhook | Not available | Available | Available |
Common errors
404 — Message not found
{}
Solution: Verify that the customerMessageId is correct and that the channel parameter matches the channel used for sending. Very old messages may no longer be available.
Wrong channel in channel parameter
If you send a message via SMS but query the status with channel=RCS, you will get a 404 even if the message exists. Make sure the channel in the query matches the one used for sending.
Persistent ACCEPTED or UNKNOWN status
Until the receipt arrives, a message reads ACCEPTED on SMS and UNKNOWN on RCS and WhatsApp. What a long wait means depends on the channel:
- RCS and WhatsApp:
UNKNOWNis the normal in-flight state, not a routing problem. These channels have no intermediate status, so the message staysUNKNOWNuntil it becomesDELIVEREDor fails, which can take a while (for example, if the recipient's phone is off). - SMS: if the status remains
ACCEPTEDfor more than 60 seconds, the carrier has not returned a receipt yet. Check:- The recipient's number is valid and reachable
- The recipient's carrier is supported
- There are no ongoing network issues
Expected result
| Step | Action | Result |
|---|---|---|
| 1 | POST /sms/send (or RCS/WhatsApp) | messageId saved |
| 2 | GET /messages/status/{id}?channel=SMS | deliveryStatus: "DELIVERED" |
| 3 | GET /messages/status?channel=SMS&ids=id1,id2,id3 | Array of statuses for each message |
Next steps
- UC-001 — Send Single SMS: Complete SMS send and verify scenario
- UC-002 — Send RCS Rich Card: Rich card with media and interactive buttons
- UC-003 — Send WhatsApp Template: Template with status tracking
- Webhook Guide: Configure webhooks for real-time notifications
- Authentication Guide: API Key and Basic Auth setup