RCS Studio can forward real-time events to your server — inbound user messages, delivery receipts, and connector activity. Configure a webhook URL on your agent and RCS Studio will POST each event to that endpoint as it occurs.
Event overview
The diagram below shows a typical exchange and when each event fires relative to the conversation.
sequenceDiagram
actor User
participant R as RCS Studio
participant S as Your server
User->>R: Sends text or taps a chip
R->>User: Agent reply
R->>S: UserMessage event
Note right of S: X-Vibes-Response header present<br/>because the agent replied
alt Delivery confirmed
R->>S: UserEvent (DELIVERED)
else Delivery fails
R->>S: ServerEvent (FAILED)
end
User->>R: Taps another chip
R->>User: Agent reply
R->>S: UserMessage event
R->>S: UserEvent (DELIVERED)
Three event classes:
| Event class | When it fires | Triggers agent processing |
|---|---|---|
UserMessage | User sends a message, taps a chip, shares location, or uploads a file | Yes — the agent resolves state and sends a reply. X-Vibes-Response is set if a reply was sent. |
UserEvent | User-side events — delivery confirmation (DELIVERED), read receipt (READ), typing indicator (IS_TYPING), and subscribe/unsubscribe | No — forwarded as-is |
ServerEvent | Server-side outcome of an outbound message — SENT, FAILED, and TTL events | No — forwarded as-is |
Setting up a webhook
Set the Event forwarding URL on your agent. You can do this in the RCS Studio UI under your agent's Settings tab, or via the API:
curl -X PATCH https://api.rcsstudio.ai/agents/ag_abc123 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"config": {"eventForwardUrl": "https://your-server.com/webhook"}}'Your endpoint must be publicly reachable over HTTPS and return a 2xx response. Events that fail to deliver are retried.
Request format
RCS Studio sends an HTTP POST to your endpoint for each event:
POST https://your-server.com/webhook HTTP/1.1
Host: your-server.com
User-Agent: rcs-studio/1.0
Content-Type: application/json
X-Vibes-Eventclass: UserMessage
X-Vibes-Signature: <base64-encoded HMAC-SHA512 of the request body>
X-Vibes-Response: <state ID or keyword, only present on UserMessage events when the agent replied>
{ ...event payload... }
Request headers
| Header | Description |
|---|---|
X-Vibes-Eventclass | The event class — UserMessage, UserEvent, or ServerEvent. |
X-Vibes-Signature | HMAC-SHA512 signature of the raw request body, base64-encoded. See Verifying signatures. |
X-Vibes-Response | The state ID (or keyword) of the message your agent sent in response to this event. Only present on UserMessage events when the agent triggered a reply. Use this to correlate an inbound user event with the outbound message it produced. |
UserMessage events
UserMessage events fire when a user interacts with your agent. These are the only events that trigger agent processing — RCS Studio resolves the next state and sends a reply before forwarding the event to your webhook.
The payload schema varies by the type of user action. All variants share a common set of fields; additional fields are present depending on what the user did.
Common fields
| Field | Type | Description |
|---|---|---|
agentId | string | The agent that received the message |
senderPhoneNumber | string | The user's phone number |
messageId | string | Unique identifier for this user message |
sendTime | string | ISO 8601 timestamp of when the message was sent |
richMessageClassification | object | Message classification — classificationType (RICH_MESSAGE or RICH_MEDIA_MESSAGE) and segmentCount |
Variant fields
| Field | Type | Present when |
|---|---|---|
text | string | User sent a text message |
suggestionResponse.text | string | User tapped a chip — the chip's display label |
suggestionResponse.postbackData | string | User tapped a chip — the postback data set on the chip |
suggestionResponse.type | string | User tapped a chip — "REPLY" for suggested reply chips, "ACTION" for action chips |
location.latitude | string | User shared their location |
location.longitude | string | User shared their location |
userFile.payload.mimeType | string | User uploaded a file |
userFile.payload.fileSizeBytes | number | User uploaded a file |
userFile.payload.fileName | string | User uploaded a file |
userFile.payload.fileUri | string | User uploaded a file — URL to the file content |
userFile.thumbnail | object | User uploaded an image or video — contains mimeType, fileSizeBytes, and fileUri |
Example — text message
POST /webhook HTTP/1.1
X-Vibes-Eventclass: UserMessage
X-Vibes-Signature: <signature>
X-Vibes-Response: order-status
{
"agentId": "example_agent",
"senderPhoneNumber": "+15551234567",
"messageId": "MxZIMfKVnURVm7GEMvpbaIng",
"sendTime": "2026-04-28T14:30:00.000000Z",
"text": "Check my order",
"richMessageClassification": {
"classificationType": "RICH_MESSAGE",
"segmentCount": 1
}
}
Example — chip tap
POST /webhook HTTP/1.1
X-Vibes-Eventclass: UserMessage
X-Vibes-Signature: <signature>
X-Vibes-Response: order-status
{
"agentId": "example_agent",
"senderPhoneNumber": "+15551234567",
"messageId": "66ecf984-5698-4256-963a-f2048c90b7fe",
"sendTime": "2026-04-28T14:30:00.000000Z",
"richMessageClassification": {
"classificationType": "RICH_MESSAGE",
"segmentCount": 1
},
"suggestionResponse": {
"postbackData": "check_order",
"text": "Check my order",
"type": "REPLY"
}
}
Example — location share
POST /webhook HTTP/1.1
X-Vibes-Eventclass: UserMessage
X-Vibes-Signature: <signature>
{
"agentId": "example_agent",
"senderPhoneNumber": "+15551234567",
"messageId": "3d184cb5-c4be-475c-9c20-04bc5240014a",
"sendTime": "2026-04-28T14:30:00.000000Z",
"richMessageClassification": {
"classificationType": "RICH_MESSAGE",
"segmentCount": 1
},
"location": {
"latitude": "37.7749",
"longitude": "-122.4194"
}
}
Example — file share
POST /webhook HTTP/1.1
X-Vibes-Eventclass: UserMessage
X-Vibes-Signature: <signature>
{
"agentId": "example_agent",
"senderPhoneNumber": "+15551234567",
"messageId": "Mx1cxm-SCFRtuiHK8mqJw0Nw",
"sendTime": "2026-04-28T14:30:00.000000Z",
"richMessageClassification": {
"classificationType": "RICH_MEDIA_MESSAGE",
"segmentCount": 1
},
"userFile": {
"payload": {
"mimeType": "image/jpeg",
"fileSizeBytes": 204800,
"fileName": "photo.jpg",
"fileUri": "https://storage.googleapis.com/rbm-uploads/..."
},
"thumbnail": {
"mimeType": "image/jpeg",
"fileSizeBytes": 4096,
"fileUri": "https://storage.googleapis.com/rbm-uploads/..."
}
}
}
UserEvent events
UserEvent events are fired by the user's device — delivery confirmations, read receipts, typing indicators, and subscribe/unsubscribe notifications. They do not trigger any agent processing; RCS Studio forwards them to your webhook unchanged.
Fields
| Field | Type | Description |
|---|---|---|
agentId | string | The agent associated with this event |
senderPhoneNumber | string | The user's phone number |
messageId | string | The ID of the message this event relates to |
eventType | string | One of: DELIVERED, READ, IS_TYPING, SUBSCRIBE, UNSUBSCRIBE |
eventId | string | Unique identifier for this event |
sendTime | string | ISO 8601 timestamp |
Example — DELIVERED
POST /webhook HTTP/1.1
X-Vibes-Eventclass: UserEvent
X-Vibes-Signature: <signature>
{
"agentId": "example_agent",
"sendTime": "2026-05-18T00:12:42.000000Z",
"senderPhoneNumber": "+15551234567",
"eventType": "DELIVERED",
"eventId": "MxkiHGGOfhSvSi3xIsj-26MQ",
"messageId": "be0ed04a-ff3c-4a1a-8c64-9235fb4f9073"
}
ServerEvent events
ServerEvent events report the server-side outcome of an outbound agent message. They do not trigger any agent processing; RCS Studio forwards them to your webhook unchanged.
ServerEventpayloads usephoneNumber(notsenderPhoneNumber) for the recipient's number.
Fields
| Field | Type | Description |
|---|---|---|
agentId | string | The agent that sent the message |
phoneNumber | string | The recipient's phone number |
messageId | string | The ID of the outbound message |
eventType | string | One of: SENT, FAILED, TTL_EXPIRATION_REVOKED, TTL_EXPIRATION_REVOKE_FAILED |
eventId | string | Unique identifier for this event |
eventOrigin | string | Source of the event — VIBES or CARRIER |
sendTime | string | ISO 8601 timestamp |
richMessageClassification | object | (SENT only) Classification of the sent message |
errorCode | string | (FAILED, conditional) HTTP status code returned by the RCS platform, as a string |
errorMessage | string | (FAILED, conditional) Raw HTTP response body from the RCS platform |
Example — SENT
POST /webhook HTTP/1.1
X-Vibes-Eventclass: ServerEvent
X-Vibes-Signature: <signature>
{
"agentId": "example_agent",
"sendTime": "2026-05-18T00:12:42.000000Z",
"phoneNumber": "+15551234567",
"messageId": "be0ed04a-ff3c-4a1a-8c64-9235fb4f9073",
"eventType": "SENT",
"eventId": "75078f52-5ed0-4d95-95d8-0cb5a7c7dede",
"eventOrigin": "VIBES",
"richMessageClassification": {
"classificationType": "RICH_MESSAGE",
"segmentCount": 1
}
}
FAILED
Fired when an agent message cannot be delivered. The errorCode and errorMessage fields are present when the failure occurred at the RCS platform level — for example, the number is not an RCS subscriber. They are absent when the failure was detected before the send attempt was made.
Example — platform rejection
The most common failure case. The send reached the RCS platform but was rejected — for example, because the destination number is not an RCS subscriber.
POST /webhook HTTP/1.1
X-Vibes-Eventclass: ServerEvent
X-Vibes-Signature: <signature>
{
"agentId": "example_agent",
"phoneNumber": "+15551234567",
"messageId": "f344c24b-4871-11f1-a4e3-000000000043",
"eventType": "FAILED",
"eventId": "de98421c-a54f-4469-b667-9d9e819444d2",
"eventOrigin": "VIBES",
"errorCode": "404",
"errorMessage": "404 Not Found\nPOST https://us-rcsbusinessmessaging.googleapis.com/v1/phones/...",
"sendTime": "2026-05-18T00:12:42.014957638Z"
}
Example — pre-delivery failure
The failure was detected before the message reached the RCS platform — for example, a capability check determined the device does not support RCS.
POST /webhook HTTP/1.1
X-Vibes-Eventclass: ServerEvent
X-Vibes-Signature: <signature>
{
"agentId": "example_agent",
"phoneNumber": "+15551234567",
"messageId": "f344c24b-4871-11f1-a4e3-000000000043",
"eventType": "FAILED",
"eventId": "de98421c-a54f-4469-b667-9d9e819444d2",
"eventOrigin": "VIBES",
"sendTime": "2026-05-18T00:12:42.014957638Z"
}
Always treat errorCode and errorMessage as optional — check eventType === "FAILED" first, then inspect the error fields for additional detail:
function handleWebhookEvent(headers, payload) {
if (headers['X-Vibes-Eventclass'] === 'ServerEvent' && payload.eventType === 'FAILED') {
const detail = payload.errorCode
? `${payload.errorCode} — ${payload.errorMessage}`
: 'No error detail available';
console.error(`Message ${payload.messageId} failed: ${detail}`);
}
}Verifying signatures
Every webhook request includes an X-Vibes-Signature header with an HMAC-SHA512 signature of the raw request body, encoded as base64.
Webhook signing tokens are not yet self-serve. To receive a signing token for your account, contact your Vibes representative. Once issued, the token is tied to your account and applies to all agents under it. Until you have a token, you can still receive and process events — signature verification is optional but strongly recommended for production use.
Once you have your signing token:
- Read the raw request body before parsing it as JSON
- Compute
HMAC-SHA512(body, signingToken)and base64-encode the result - Compare your computed value to the
X-Vibes-Signatureheader value - Reject requests where the signatures do not match — return
403
import crypto from 'crypto';
function verifySignature(rawBody, signature, signingToken) {
const expected = crypto
.createHmac('sha512', signingToken)
.update(rawBody)
.digest('base64');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}HTTP responses
Return a 2xx status code to acknowledge receipt.
4xxresponses are treated as permanent failures — the event moves to the failure queue without retrying. Return5xxif you need a transient failure to trigger a retry.
Acknowledge events quickly and process them asynchronously if the work is time-consuming — long-running handlers risk timing out and triggering unnecessary retries.
See also
- Update your agent — Configure the Event forwarding URL in the RCS Studio UI
- PATCH /agents/:agentId — Set
eventForwardUrlvia the API