Webhooks
This page documents customer-facing webhooks for API integrations.
Webhooks are the lifecycle source of truth after a send request is accepted. A synchronous send response tells you the request was queued. Webhooks tell you what happened after that.
What webhooks are for
Use customer-facing webhooks to:
- track outbound status progression after
QUEUED - detect final delivery or failure outcomes
- receive inbound customer messages
- correlate platform events back to your own business records
If you send messages but do not consume webhooks, your integration is missing the final delivery and failure lifecycle.
Customer-facing webhook types
Outbound lifecycle
message.queuedmessage.sentmessage.deliverymessage.failed
Inbound message
message.inbound
Typical outbound lifecycle
For a normal successful outbound send, you should expect a sequence similar to:
message.queuedmessage.sentmessage.delivery
For a failed outbound send, you should expect the sequence to terminate with message.failed.
Treat sequence as the ordering field for outbound lifecycle handling.
Signing headers
When signing is enabled, Red Cloud sends:
X-RedCloud-TimestampX-RedCloud-Signature
Signing formula:
HMAC_SHA256(secret, "${timestamp}.${rawBody}")
Where:
timestampis the value ofX-RedCloud-TimestamprawBodyis the exact JSON body delivered to your endpoint
Signature verification requirements
- verify against the exact raw request body, not a re-serialized object
- reject requests missing signing headers
- reject requests with invalid signatures
- return a non-
2xxresponse when signature verification fails
If your framework parses JSON automatically, make sure you still preserve the raw body used in the HMAC calculation.
Retry/backoff behavior
Webhook delivery is queue-based.
Current retry schedule:
- 1 minute
- 5 minutes
- 15 minutes
- 60 minutes
- then terminal failure
Operational implication
Your webhook endpoint should:
- return
2xxonly after the event is durably accepted by your system - return non-
2xxwhen processing failed and a retry is appropriate - avoid long synchronous work before acknowledging receipt
If you need heavier downstream processing, acknowledge quickly and continue inside your own system.
Canonical payload snapshots
message.queued
{
"eventType": "message.queued",
"direction": "outbound",
"occurredAt": 1775002218000,
"apiClientId": "rc_test_client_123",
"messageId": "msg_abc123",
"sequence": 1,
"data": {
"messageId": "msg_abc123",
"externalReference": "order_789",
"sendFrom": "+15551234567",
"to": "+15551234567",
"status": "QUEUED",
"error": null
}
}
message.sent
{
"eventType": "message.sent",
"direction": "outbound",
"occurredAt": 1775002218200,
"apiClientId": "rc_test_client_123",
"messageId": "msg_abc123",
"sequence": 2,
"data": {
"messageId": "msg_abc123",
"externalReference": "order_789",
"sendFrom": "+15551234567",
"to": "+15551234567",
"status": "sent",
"error": null
}
}
message.delivery
{
"eventType": "message.delivery",
"direction": "outbound",
"occurredAt": 1775002218400,
"apiClientId": "rc_test_client_123",
"messageId": "msg_abc123",
"sequence": 3,
"data": {
"messageId": "msg_abc123",
"externalReference": "order_789",
"sendFrom": "+15551234567",
"to": "+15551234567",
"status": "delivered",
"error": null,
"providerMessageRef": "provider_msg_123"
}
}
message.failed
{
"eventType": "message.failed",
"direction": "outbound",
"occurredAt": 1775002218600,
"apiClientId": "rc_test_client_123",
"messageId": "msg_abc123",
"sequence": 4,
"data": {
"messageId": "msg_abc123",
"externalReference": "order_789",
"sendFrom": "+15551234567",
"to": "+15551234567",
"status": "failed",
"error": {
"code": "PROVIDER_SEND_FAILED",
"message": "Provider send failed",
"details": {}
}
}
}
message.inbound
{
"eventType": "message.inbound",
"direction": "inbound",
"occurredAt": 1775002218800,
"apiClientId": "rc_test_client_123",
"data": {
"messageId": "msg_abc123",
"conversationId": "conv_abc123",
"numberId": "num_abc123",
"from": "+15551234567",
"to": "+15551234567",
"body": "Reply from customer",
"media": [],
"timestamp": 1775002218800
}
}
Field explanation
Common fields you should expect:
eventType- lifecycle event name
directionoutboundorinbound
occurredAt- event timestamp in epoch milliseconds
apiClientId- Red Cloud API client identifier
messageId- Red Cloud message identifier
sequence- outbound event ordering field
Recommended correlation fields
For outbound events, persist and use:
messageIdexternalReferencesequence
For inbound events, persist and use:
messageIdconversationIdnumberId- sender and recipient values
Recommended webhook handler behavior
For every webhook request:
- capture the raw body
- verify signature headers
- parse the payload
- log or persist
eventType,messageId, andoccurredAt - apply idempotent event handling in your own system
- return
200only after the event is accepted internally
At minimum, your integration should avoid duplicate downstream effects when a webhook delivery is retried.
Common webhook mistakes
- treating the synchronous send response as final delivery
- verifying a re-serialized JSON body instead of the raw body
- returning
200before the event is actually accepted internally - ignoring
sequencefor outbound lifecycle handling - not persisting
messageIdandexternalReference - doing heavy downstream work before sending the HTTP response
Webhook boundary
Customer-facing webhook endpoints are configured by the customer and receive signed Red Cloud deliveries.
Internal platform ingestion paths are not part of the public API surface and are intentionally undocumented here.