Delivery Tracking
Overview
Every operation dispatched through the Integrations module - a payment initiation, an SMS send, a WhatsApp message - produces a delivery attempt record. This record tracks the operation from the moment it enters the queue through each retry cycle until it either reaches the provider successfully or exhausts its retry budget. Delivery attempts give you full visibility into the async pipeline without requiring access to provider dashboards.
Because delivery is asynchronous, the API response to a payment or messaging request confirms that the operation was accepted and queued, not that it was delivered. The delivery attempt record is how you determine the actual outcome. Poll the attempt or filter the list endpoint regularly to catch failures early, before they affect your end users.
Delivery Status Lifecycle
| Status | Terminal | Description |
|---|---|---|
| pending | No | Operation queued; no delivery has been attempted yet |
| in_progress | No | Currently being processed and sent to the provider |
| delivered | Yes | Provider confirmed successful delivery |
| failed | No | Most recent attempt failed; retries remain in the budget |
| retrying | No | Scheduled for the next retry; waiting for next_retry_at |
| exhausted | Yes | All retries used; operation will not be attempted again |
Terminal States
delivered and exhausted are terminal. Once a delivery attempt reaches either status, no further processing occurs. An exhausted attempt requires a new operation to be triggered from your application if the business action still needs to complete.
What Creates a Delivery Attempt
A delivery attempt record is created automatically in the following situations:
Payment operations: When a payment is initiated through the Integrations module, a delivery attempt is created for the initiate_payment capability. If the payment includes a refund or verification step routed through the module, separate attempts are created for those capabilities.
SMS messages: When an SMS send is triggered and the routing engine selects a provider, a delivery attempt is created for the send_sms capability. Bulk sends to multiple recipients create one attempt per recipient.
WhatsApp messages: When a WhatsApp message is triggered, a delivery attempt is created for the send_whatsapp capability. The same per-recipient rule applies as SMS.
API Endpoints
List Delivery Attempts
Returns all delivery attempts for your tenant. Supports filtering by capability, status, and date range for efficient investigation.
Endpoint: GET /api/v1/delivery-attempts
Authentication: Required (Bearer token)
Query Parameters:
capability(string) - Filter by capability:initiate_payment,process_refund,verify_payment,send_sms,send_whatsappstatus(string) - Filter by status:pending,in_progress,delivered,failed,retrying,exhaustedintegration_id(string) - Filter by the integration that handled the attemptfrom(string) - ISO 8601 date; only return attempts created on or after this dateto(string) - ISO 8601 date; only return attempts created on or before this dateper_page(integer) - Results per page (default: 25, max: 100)page(integer) - Page number
Example Request:
GET /api/v1/delivery-attempts?capability=send_sms&status=exhausted&from=2026-02-01Response (200 OK):
{
"success": true,
"data": [
{
"id": "01hwatmp1abc456def789ghi00",
"capability": "send_sms",
"status": "exhausted",
"integration_id": "01hwxyz789abc012def345ghi6",
"integration": {
"id": "01hwxyz789abc012def345ghi6",
"provider": "plivo",
"display_name": "Plivo"
},
"retry_count": 3,
"next_retry_at": null,
"error_message": "Provider returned 401 Unauthorized after 3 attempts",
"provider_response": {
"status_code": 401,
"message": "Authentication credentials were not accepted."
},
"created_at": "2026-02-03T07:12:00Z",
"updated_at": "2026-02-03T07:45:00Z"
},
{
"id": "01hwatmp2abc456def789ghi00",
"capability": "send_sms",
"status": "exhausted",
"integration_id": "01hwxyz789abc012def345ghi6",
"integration": {
"id": "01hwxyz789abc012def345ghi6",
"provider": "plivo",
"display_name": "Plivo"
},
"retry_count": 3,
"next_retry_at": null,
"error_message": "Provider returned 401 Unauthorized after 3 attempts",
"provider_response": {
"status_code": 401,
"message": "Authentication credentials were not accepted."
},
"created_at": "2026-02-03T07:13:00Z",
"updated_at": "2026-02-03T07:46:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 14,
"last_page": 1
},
"links": {
"first": "/api/v1/delivery-attempts?page=1",
"last": "/api/v1/delivery-attempts?page=1",
"prev": null,
"next": null
}
}Get Delivery Attempt
Returns full details for a single delivery attempt including the provider response and error message, if any. The payload field - which contains the full outbound request data - is intentionally omitted from the response to avoid exposing sensitive operation details.
Endpoint: GET /api/v1/delivery-attempts/{id}
Authentication: Required (Bearer token)
Example Request:
GET /api/v1/delivery-attempts/01hwatmp1abc456def789ghi00Response (200 OK):
{
"success": true,
"data": {
"id": "01hwatmp1abc456def789ghi00",
"capability": "send_sms",
"status": "exhausted",
"integration_id": "01hwxyz789abc012def345ghi6",
"integration": {
"id": "01hwxyz789abc012def345ghi6",
"provider": "plivo",
"display_name": "Plivo",
"status": "error"
},
"retry_count": 3,
"next_retry_at": null,
"error_message": "Provider returned 401 Unauthorized after 3 attempts",
"provider_response": {
"status_code": 401,
"message": "Authentication credentials were not accepted.",
"request_uuid": null
},
"created_at": "2026-02-03T07:12:00Z",
"updated_at": "2026-02-03T07:45:00Z"
},
"meta": {}
}Example - In-progress attempt:
{
"success": true,
"data": {
"id": "01hwatmp3abc456def789ghi00",
"capability": "initiate_payment",
"status": "retrying",
"integration_id": "01hwxyz123abc456def789ghi0",
"integration": {
"id": "01hwxyz123abc456def789ghi0",
"provider": "stripe",
"display_name": "Stripe",
"status": "active"
},
"retry_count": 1,
"next_retry_at": "2026-02-10T15:05:00Z",
"error_message": "Provider timeout on attempt 1",
"provider_response": null,
"created_at": "2026-02-10T14:55:00Z",
"updated_at": "2026-02-10T14:58:00Z"
},
"meta": {}
}Reading Provider Responses
The provider_response field contains the raw response data returned by the external provider on the most recent attempt. Its structure varies by provider and capability.
Interpreting provider_response
provider_response is stored as-is from the provider. Payment providers typically return a status code and an error message or transaction identifier. Communication providers often include a message UUID or delivery report code. Refer to your provider's documentation for the full list of status codes.
A null value for provider_response means the attempt has not yet received a response from the provider - this is normal for pending and in_progress statuses, and can also occur if the network connection to the provider timed out before a response was received.
The error_message field is a normalised description written by the platform summarising what went wrong, making it useful for display in dashboards and alerts regardless of which provider was involved.
Business Scenarios
Scenario 1: Monitoring a Failed SMS Batch
Context: A promotional SMS campaign was sent to 500 recipients yesterday morning. Your team reports that some customers did not receive the message.
Workflow:
- List all
exhaustedandfailedSMS delivery attempts from the campaign date - Identify the affected count and the common error pattern in
provider_response - Determine whether the issue was a credential failure (affecting all messages) or a recipient-level failure (e.g., invalid phone number)
- Address the root cause - rotate credentials if it was an auth error, validate recipient lists if it was a number format issue
API Calls:
# 1. List failed attempts for send_sms on the campaign date
GET /api/v1/delivery-attempts?capability=send_sms&status=exhausted&from=2026-02-08&to=2026-02-08
# 2. Inspect a sample attempt for the full provider response
GET /api/v1/delivery-attempts/01hwatmp1abc456def789ghi00
# 3. If credential error - check integration status
GET /api/v1/integrations/01hwxyz789abc012def345ghi6
# 4. If integration is in error state - update credentials to re-trigger verification
PATCH /api/v1/integrations/01hwxyz789abc012def345ghi6
{
"credentials": {
"auth_token": "auth_token_corrected_placeholder"
}
}Scenario 2: Payment Delivery Investigation
Context: A customer has reported that their payment was deducted from their bank account but your application shows the order as unpaid. You need to determine whether the payment delivery attempt succeeded and what the provider recorded.
Workflow:
- Retrieve delivery attempts filtered to
initiate_paymentfor the relevant time window - Find the attempt for the customer's order and check its status and
provider_response - If
delivered, the provider recorded success - investigate your application's reconciliation logic - If
exhausted, the provider did not confirm receipt - investigate theprovider_responsefor a partial authorization code or timeout
API Calls:
# 1. List recent payment attempts
GET /api/v1/delivery-attempts?capability=initiate_payment&from=2026-03-10&to=2026-03-11
# 2. Retrieve the specific attempt
GET /api/v1/delivery-attempts/01hwatmp5abc456def789ghi00Best Practices
1. Poll for Final Status Before Acting
Do not treat the HTTP response from a payment or messaging API call as confirmation of delivery. Always retrieve the delivery attempt and wait for delivered status before marking an order as paid or a notification as sent. Design your application to handle asynchronous confirmation gracefully.
2. Monitor for Exhausted Attempts Regularly
Schedule a daily review of exhausted delivery attempts using a date-range filter. A spike in exhausted attempts for a specific capability or integration is an early warning of credential issues, provider outages, or routing misconfigurations. Catching these patterns early prevents prolonged disruptions.
3. Use Date Ranges to Scope Investigations
The from and to query parameters are essential for performance when your tenant has a large volume of delivery attempts. Always scope investigations to the relevant time window rather than paginating through the full history. Start broad (the day of the incident) and narrow down.
Troubleshooting
All Attempts for a Capability are Exhausted
Symptom: A large number of attempts for send_sms or initiate_payment have all reached exhausted status within a short time window.
Possible Causes:
- The integration's credentials were revoked or expired in the provider dashboard
- The provider is experiencing an outage
- A routing rule change caused operations to land on an integration with invalid credentials
Resolution:
- List recent exhausted attempts and inspect
provider_response:
GET /api/v1/delivery-attempts?status=exhausted&capability=send_sms&from=2026-03-10- If
provider_responseshows a401or authentication error, check and update the integration credentials:
GET /api/v1/integrations/{integration_id}
PATCH /api/v1/integrations/{integration_id}
{
"credentials": {
"auth_token": "auth_token_corrected_placeholder"
}
}- If
provider_responseshows a5xxerror, the provider is likely experiencing an outage. Check the provider's status page and consider activating a fallback integration via your routing rules.
Attempt Stuck in retrying
Symptom: A delivery attempt has been in retrying status for longer than expected and next_retry_at is in the past.
Possible Causes:
- The retry job queue is backed up due to high volume
- The retry scheduler is encountering errors before dispatching the job
Resolution:
- Retrieve the attempt to confirm the current state and
next_retry_at:
GET /api/v1/delivery-attempts/01hwatmp3abc456def789ghi00- If
next_retry_atis more than 15 minutes in the past and status has not changed, the job may be stuck in the queue. Check your application's queue monitoring tooling for worker health. - If the business action is time-sensitive and cannot wait, trigger a new operation from your application rather than waiting for the stuck retry to complete. Track the new attempt using the updated delivery attempt ID.
Related Documentation
- Managing Integrations - Configure providers and manage credential status
- Routing Rules - Control which integration handles each capability
- Integrations Overview - Module architecture and supported providers