Skip to main content

UC-004 — Check Delivery Status

FieldValue
IDUC-004
GoalCheck message delivery status with polling and webhooks
ChannelMulti-channel (SMS, RCS, WhatsApp)
ComplexityBasic
Estimated time15 minutes
APIs involvedGET /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:

:::tip Test without costs Add "simulation": true in the request body to validate the flow without actually sending messages and without consuming credit. :::

Polling vs Webhook

FeaturePollingWebhook
DirectionYour server calls the APIThe API calls your server
LatencyDepends on polling intervalReal time
API loadProportional to the number of callsOne call per event
ComplexityLow (just a loop)Medium (requires a public endpoint)
Ideal forSpot checks, debug, small volumesProduction, high volumes, real-time

:::tip Which one to choose? 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://lora-api.agiletelecom.com/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:

  1. SENT — The gateway accepted the message and forwarded it to the carrier.
  2. DELIVERED — The carrier confirms delivery to the recipient's device.
  3. READ (RCS/WhatsApp only) — The recipient has opened and viewed the message.
  4. ERROR — The carrier reports a delivery error.
  5. EXPIRED — The message was not delivered within the TTL (Time To Live).

The SENT -> DELIVERED transition 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://lora-api.agiletelecom.com/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": "Message delivered to handset",
"sendDate": "2026-04-09T15:00:00+02:00",
"deliveryDate": "2026-04-09T15:00:03+02:00",
"readDate": null
}

Response — ERROR

{
"customerMessageId": "d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890",
"channel": "SMS",
"destination": "+393471234567",
"deliveryStatus": "ERROR",
"deliveryStatusDescription": "Undeliverable",
"sendDate": "2026-04-09T15:00:00+02:00",
"deliveryDate": null,
"readDate": null
}
Behind the scenes — Complete status table
StatusWebhook codeChannelDescription
DELIVERED3SMS, RCS, WhatsAppMessage delivered successfully
SENTAllMessage sent, waiting for carrier confirmation
RECEIVEDRCS, WhatsAppMessage received by the WhatsApp/RCS server
ERROR6AllDelivery error (non-existent number, unreachable network)
EXPIRED8AllTTL expired, the message was not delivered in time
UNKNOWNAllStatus not yet determined

Note on webhook codes: The numeric statusCode values (3, 6, 8) are used in webhook callbacks. In the status polling API, the deliveryStatus field is a human-readable string.

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://lora-api.agiletelecom.com/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": "Message 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": "Message 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": "ERROR",
"deliveryStatusDescription": "Undeliverable",
"sendDate": "2026-04-09T15:00:01+02:00",
"deliveryDate": null,
"readDate": null
}
]

:::note IDs not found 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 customerMessageId to 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 every 3 seconds, with a 30-second timeout:

#!/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://lora-api.agiletelecom.com/api/partner-gateway/v1/messages/status/${MESSAGE_ID}?channel=SMS" \
-H "X-Api-Key: ${API_KEY}" | jq -r '.deliveryStatus')

echo "Status: $STATUS"

if [ "$STATUS" = "DELIVERED" ]; then
echo "Message delivered!"
exit 0
elif [ "$STATUS" = "ERROR" ] || [ "$STATUS" = "EXPIRED" ]; then
echo "Delivery error: $STATUS"
exit 1
fi

sleep 3
done

echo "Timeout: final status not reached"
exit 2

Channel comparison

FieldSMSRCSWhatsApp
deliveryStatusDELIVERED, SENT, ERROR, EXPIREDDELIVERED, SENT, ERROR, EXPIREDDELIVERED, SENT, RECEIVED, ERROR, EXPIRED
readDateAlways nullPresent if readPresent if read
Typical DELIVERED latency1-5 seconds0.5-2 seconds0.5-2 seconds
READ webhookNot availableAvailableAvailable

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 UNKNOWN status

If the status remains UNKNOWN for more than 60 seconds, it may indicate a routing problem. Check:

  • The recipient's number is valid and reachable
  • The recipient's carrier is supported
  • There are no ongoing network issues

Expected result

StepActionResult
1POST /sms/send (or RCS/WhatsApp)messageId saved
2GET /messages/status/{id}?channel=SMSdeliveryStatus: "DELIVERED"
3GET /messages/status?channel=SMS&ids=id1,id2,id3Array of statuses for each message

Next steps