External API
VOXIO CHAT EXTERNAL API

Build more possibilities

Connect Voxio Chat to your CRM, helpdesk, automation platform, or custom application. Build customer experiences across WhatsApp, Instagram, and Messenger using one secure API.

✓ Secure & reliable ✓ Omnichannel ready ✓ Scoped access
Recommended

OAuth 2.0 + PKCE

Modern authorization for third-party applications, CRM integrations, automation tools, and public clients without sharing the legacy Client Secret.

  • Authorization Code + PKCE (S256)
  • Granular scopes
  • Access and refresh tokens
  • No legacy IP whitelist requirement for Bearer authentication
Get started with OAuth 2.0 →
Legacy / Existing integrations

Legacy API Credentials

Existing server-to-server integrations can continue using Client ID + Client Secret with the workspace IP whitelist.

  • Client ID + Client Secret
  • IP whitelist protection
  • Existing legacy endpoints remain available
  • Recommended only for existing integrations
View legacy authentication →
GETTING STARTED

Welcome to Voxio Chat External API

The modern API uses portable communication primitives—channels, contacts, conversations, messages, templates, CSV Blast, and webhooks—so an external CRM or automation system can keep its own ticketing, routing, assignment, and UI.

OmnichannelWhatsApp, Instagram, Messenger
</>Developer friendlyREST + JSON + consistent contracts
Secure by designOAuth, scopes, HMAC, tenant isolation
Built for growthMessaging primitives without CRM lock-in
QUICK EXAMPLE

Read the connected workspace

profile:read

After the OAuth authorization code has been exchanged for an access token, call the v1 API with a Bearer token.

cURL
curl -X GET 'https://www.voxio.chat/external-api/v1/me' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Accept: application/json'
Example response
{
  "remark": "oauth_profile",
  "status": "success",
  "data": {
    "workspace": { "id": 11 },
    "capabilities": {
      "api_access": true
    }
  }
}
1Authorize/oauth/authorize
2User consentWorkspace owner approves scopes
3Exchange codePOST /oauth/token
4Call APIAuthorization: Bearer …
Token lifecycle Access tokens expire after 1 hour. Refresh tokens expire after 60 days. Refresh token rotation does not grant additional scopes; request new authorization when additional permissions are needed.

Authorization request

HTTPS
https://www.voxio.chat/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_CALLBACK&scope=profile%3Aread%20channels%3Aread&code_challenge=YOUR_S256_CHALLENGE&code_challenge_method=S256&state=RANDOM_STATE

Token exchange

cURL
curl -X POST 'https://www.voxio.chat/oauth/token' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'client_id=YOUR_CLIENT_ID' \
  --data-urlencode 'redirect_uri=YOUR_CALLBACK' \
  --data-urlencode 'code=AUTHORIZATION_CODE' \
  --data-urlencode 'code_verifier=ORIGINAL_PKCE_VERIFIER'
ScopeAccess
profile:readWorkspace profile and API capability information.
channels:readConnected WhatsApp, Instagram, and Messenger channels.
contacts:read / contacts:writeRead, create, and update customer contacts.
conversations:read / conversations:writeRead conversations and update portable conversation state.
messages:read / messages:sendRead message history/media and send supported messages.
templates:readRead approved WhatsApp templates.
csv_blast:read / csv_blast:writeRead and create CSV Blast campaigns.
webhooks:read / webhooks:writeRead and manage omnichannel webhook subscriptions. Requires current Inbound Relay entitlement.
connection:read / connection:revokeRead OAuth connection metadata or revoke the current connected application.
401Missing or invalid authentication
403Scope, entitlement, or policy denied
404Resource not found or not visible to this tenant
409State or idempotency conflict
422Validation or business-rule rejection
429Rate limit exceeded
JSON
{
  "remark": "not_found",
  "status": "error",
  "message": ["The requested resource was not found."],
  "data": {
    "trace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  }
}
profile:readTenant-bound OAuth profile endpoint.
channels:readOptional filter: ?platform=whatsapp|instagram|messenger
MethodEndpointScope
GET/contactscontacts:read
GET/contacts/{id}contacts:read
POST/contactscontacts:write
PATCH/contacts/{id}contacts:write
Identity safetyInstagram and Messenger managed identities cannot be arbitrarily changed through the API. Cross-workspace resources are not visible.
MethodEndpointScope
GET/conversationsconversations:read
GET/conversations/{id}conversations:read
PATCH/conversations/{id}conversations:write

Portable status values are open and closed. List filters include channel, channel ID, contact ID, status, and incremental updated_since.

MethodEndpointScope
GET/conversations/{conversation}/messagesmessages:read
POST/conversations/{conversation}/messagesmessages:send
GET/messages/{message}messages:read
GET/messages/{message}/mediamessages:read

Idempotent send

cURL
curl -X POST 'https://www.voxio.chat/external-api/v1/conversations/CONVERSATION_ID/messages' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: UNIQUE_REQUEST_KEY' \
  --data '{"type":"text","text":"Hello from my integration"}'
MethodEndpointScope
GET/templatestemplates:read
GET/templates/{template}templates:read
POST/conversations/{conversation}/messages with type=templatemessages:send
MethodEndpointScope
GET/csv-blastscsv_blast:read
POST/csv-blastscsv_blast:write
GET/csv-blasts/{blast}csv_blast:read
GET/csv-blasts/{blast}/recipientscsv_blast:read
IdempotencySupply Idempotency-Key when creating a blast. Replaying the same request returns the existing blast; reusing the same key for a different request returns HTTP 409.
MethodEndpointScope
GET/webhooks / /webhooks/{webhook}webhooks:read
POST/webhookswebhooks:write
PATCH/webhooks/{webhook}webhooks:write
DELETE/webhooks/{webhook}webhooks:write
POST/webhooks/{webhook}/rotate-secretwebhooks:write
POST/webhooks/{webhook}/testwebhooks:write
Automatic provisioning with OAuthOAuth applications granted webhooks:write can register or update the workspace webhook subscription programmatically after authorization. The user does not need to configure Inbound Relay manually. The application should supply its HTTPS callback URL, requested events, and selected Voxio channel IDs, then enable the subscription through this API. The workspace must still have an active Inbound Relay entitlement; OAuth scopes never bypass feature entitlement.
URL policyWebhook destinations must use HTTPS and resolve to a public IP address. Private, reserved, or unresolved destinations are rejected.
MethodEndpointScope
GET/oauth/connectionconnection:read
DELETE/oauth/connectionconnection:revoke

Revocation invalidates the application's access and refresh tokens for the workspace/client connection.

HeaderPurpose
X-Voxio-Webhook-SignatureHMAC SHA-256 of timestamp + "." + exact_raw_body.
X-Voxio-Signature-V2Compatibility alias for the same timestamp-bound HMAC.
X-Voxio-TimestampUnix timestamp included in the signed content.
X-Voxio-Delivery-IdStable delivery identifier for receiver-side deduplication.
X-Voxio-EventEvent type.
Formula
X-Voxio-Webhook-Signature = sha256=HMAC_SHA256(X-Voxio-Timestamp + "." + raw_request_body, relay_secret)
1Create/register OAuth client + callback
2Implement PKCE + state validation
3Map legacy calls to v1 resources
4Verify production behavior and revoke old access when ready
Legacy authenticationSend client-id and client-secret headers from a whitelisted public IP address. Do not place credentials in browser-side JavaScript.
php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/contact/list?whatsapp_account_id=24',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

 
                                
                        
Query Parameters

Query parameters that allow you to customize the API response.

Name Description Required Default
page Specifies the page number to retrieve. No 1
paginate Defines the number of items returned per page. No 20
search Searches for contacts by firstname, lastname or mobile number. No -
whatsapp_account_id Filter contacts by sender account scope (via linked conversations). No All
from_number Filter contacts by sender phone number scope. No All

Tip: In multi-number usage, use one of these filters so results match /inbox/conversation-list scope.

php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/contact/store',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => array(
    'firstname' => 'John',
    'lastname' => 'Doe',
    'mobile_code' => '62',
    'mobile' => '85712345678',
    'whatsapp_account_id' => '24'
  ),
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

Required Fields

The following fields are required to create a new contact in the system.

Name Required Default
firstname Yes -
lastname Yes -
mobile_code Yes -
mobile Yes -
city No -
state No -
post_code No -
address No -
profile_image No -
whatsapp_account_id No Auto-resolved to default account
from_number No Auto-resolved to default account
php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/contact/update/{contactId}',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => array(
    'firstname' => 'John',
    'lastname' => 'Doe',
    'mobile_code' => '62',
    'mobile' => '85712345678',
    'whatsapp_account_id' => '24'
  ),
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

Required Fields

The following fields are required to update a contact in the system.

Name Required Default
firstname Yes -
lastname Yes -
mobile_code Yes -
mobile Yes -
city No -
state No -
post_code No -
address No -
profile_image No -
whatsapp_account_id No Keep current/default account
from_number No Keep current/default account
php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/contact/delete/{contactId}',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'DELETE',
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/inbox/conversation-list',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

Query Parameters

Name Description Default
status Filter conversations by status. Use below value for the filter conversation via status. Done = 1; Pending = 2; Important = 3; Unread = 4; Reset/All = 0; All
whatsapp_account_id Filter conversations by specific sender account ID. All
from_number Filter conversations by specific sender number. All
page Specifies the page number to retrieve. 1
paginate Defines the number of items returned per page. 20
php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/inbox/conversation-messages/{conversation_id}',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

URL Parameters

Parameter Type Description
conversation_id integer Unique ID of the conversation
php

$curl = curl_init();
curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/inbox/change-conversation-status/2',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => array('status' => '1'),
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

URL Parameters

Parameter Type Description
conversation_id integer Unique ID of the conversation

Request Body

Field Type Required
status integer Yes

Allowed status values: 1 (Done), 2 (Pending), 3 (Important), 0 (Reset/None).

php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/inbox/conversation-details/2',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

URL Parameters

Parameter Type Description
conversation_id integer Unique ID of the conversation
php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/inbox/send-message',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => array(
    'mobile_code' => '880',
    'mobile' => 'xxxxxxxxx',
    'message' => 'Hello world',
    'from_number' => '6281234567890'
  ),
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;

Request Body

Field Type Required Description
mobile_code string yes Mobile country code. Must be a valid numeric country code without the plus (+) sign.
mobile string yes A valid mobile phone number associated with the provided country code.
from_number string conditional Optional sender number. Must match one of your connected WhatsApp numbers. Use this when your account has more than one number.
whatsapp_account_id integer Conditional Optional sender account ID. If both whatsapp_account_id and from_number are provided, whatsapp_account_id is prioritized.
message string Conditional Text message body. Required if no media, location, or interactive data is provided
image file No Image file (jpg, jpeg, png – max 5MB)
document file No Document file (pdf, doc, docx, xls, xlsx, txt – max 100MB)
video file No Video file (mp4 – max 16MB)
audio file No Audio file – max 16MB
latitude decimal Conditional Latitude for location message
longitude decimal Conditional Longitude for location message
name string No Optional location label used with latitude/longitude.
address string No Optional location address used with latitude/longitude.
cta_url_id integer No CTA URL ID for interactive button messages
interactive_list_id integer No Interactive list ID
product json string No WooCommerce product payload. When provided, message is sent as interactive CTA product message.
created_order_data json string No Created order payload for e-commerce CTA message generation.

Notes

At least one message type must be provided.

When using multiple sender numbers, always set from_number or whatsapp_account_id to avoid sending from the wrong number.

Interactive messages require an active plan.

Blocked contacts cannot send or receive messages.

Response Status (Success / Failed)

Field Possible Value Description
status success / error Main request result. success = accepted for send, error = failed to send request.
remark success, validation_error, not_found, exception Error or success category returned by API.
data.message.status 1, 2, 3, 9 Message state code in system: 1=SENT, 2=DELIVERED, 3=READ, 9=FAILED.

Sample Success Response

JSON
{
  "remark": "success",
  "status": "success",
  "message": [
    "Message sent successfully"
  ],
  "data": {
    "conversation_id": 123,
    "message": {
      "id": 456,
      "status": 1
    }
  }
}
                

Sample Failed Response

JSON
{
  "remark": "exception",
  "status": "error",
  "message": [
    "Something went wrong while sending message"
  ]
}
                
php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/inbox/send-template-message',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => array(
    'mobile_code' => '880',
    'mobile' => 'xxxxxx',
    'template_id' => 'your template id',
    'from_number' => '6281234567890',
    'header_variables[0]' => 'Customer Name',
    'body_variables[0]' => 'INV-1001',
    'body_variables[1]' => 'Rp500.000'
  ),
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

Request Body

Field Type Required Description
mobile_code string yes Mobile country code. Must be a valid numeric country code without the plus (+) sign.
mobile string yes A valid mobile phone number associated with the provided country code.
from_number string conditional Optional sender number. Must match one of your connected WhatsApp numbers. Use this when your account has more than one number.
whatsapp_account_id integer conditional Optional sender account ID. If both whatsapp_account_id and from_number are provided, whatsapp_account_id is prioritized.
template_id integer Yes Approved WhatsApp template ID
header_variables array<string> conditional Ordered text values for template header placeholders. Supply the values in the same order as the approved template variables.
body_variables array<string> conditional Ordered text values for template body placeholders. Supply the values in the same order as the approved template variables.

Notes

Only approved WhatsApp templates can be sent. When the template contains text placeholders, pass header_variables and/or body_variables in placeholder order.

Template ID must belong to the selected sender number/account.

Template messages are typically used for business-initiated conversations.

Blocked contacts cannot receive template messages.

WhatsApp account must be connected before sending messages.

Response Status (Success / Failed)

Field Possible Value Description
status success / error Main request result. success = accepted for send, error = failed to send request.
remark template_sent, validation_error, not_found, insufficient_balance, exception Error or success category returned by API.

Sample Success Response

JSON
{
  "remark": "template_sent",
  "status": "success",
  "message": [
    "Message sent successfully"
  ],
  "data": {
    "conversation_id": 123
  }
}
                

Sample Failed Response

JSON
{
  "remark": "insufficient_balance",
  "status": "error",
  "message": [
    "Insufficient template balance."
  ]
}
                
php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://www.voxio.chat/external-api/inbox/template-list',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'client-id: YOUR-CLIENT-ID',
    'client-secret: YOUR-CLIENT-SECRET',
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

Query Parameters

Name Description Required
whatsapp_account_id Filter templates by a specific sender account ID. No
from_number Filter templates by a specific sender number. No
conversation_id Filter templates using the sender account from a conversation. Used when sending template from chat context. No

Notes

For multi-number accounts, use one of the filter parameters above to avoid mixed template lists.

php
$payload = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_VOXIO_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_VOXIO_SIGNATURE_V2'] ?? '';
$deliveryId = $_SERVER['HTTP_X_VOXIO_DELIVERY_ID'] ?? '';

// Relay Secret is configured in Voxio Inbound Relay settings and is never sent.
$relaySecret = 'YOUR_RELAY_SECRET';

if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit('Invalid or stale timestamp');
}

$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $payload, $relaySecret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

// Persist deliveryId if you need receiver-side idempotency across retries.

$event = $_SERVER['HTTP_X_VOXIO_EVENT'] ?? 'unknown';
$data = json_decode($payload, true);

http_response_code(200);
echo json_encode(['ok' => true, 'event' => $event]);

Activation Requirements

Requirement Description
Plan / Add-on Your pricing plan or PAYG add-on must support Inbound Relay.
API Access API Access must be enabled for your account.
Client ID / Secret Optional for relay delivery. These credentials continue to authenticate calls made by your server to the legacy External API.
IP Whitelist Optional for relay delivery. IP whitelist continues to protect calls made by your server to the legacy External API.
Relay URL Your endpoint must be publicly reachable over HTTPS and should return HTTP 2xx quickly.
Relay Secret Required when enabling Inbound Relay. This secret is configured in Voxio and used as the HMAC signing key for X-Voxio-Signature-V2. It is never sent as a request header.

Relay Secret vs API Client Secret

Secret Purpose Sent in Header?
Relay Secret Used by Voxio to generate X-Voxio-Signature-V2 and by your server to verify the exact raw payload. This should be unique per relay endpoint. No
Client Secret Authenticates calls from your server to the legacy External API. It is not the webhook trust boundary. A legacy relay header may be sent temporarily for backward compatibility and can be disabled by the platform operator. Deprecated compatibility only

Headers Sent by Voxio

Header Description
X-Voxio-Event Event name, for example incoming_message or message_status depending on your selected relay events.
X-Voxio-Timestamp Unix timestamp in seconds used by Signature-V2. Reject timestamps outside a short window (for example 5 minutes) to reduce replay risk.
X-Voxio-Timestamp-Iso8601 Informational ISO-8601 timestamp provided for compatibility and logs. Signature-V2 uses X-Voxio-Timestamp, not this header.
X-Voxio-Signature-V2 Recommended HMAC SHA-256 signature over Unix timestamp + dot + exact raw body. Format: sha256=<hash>.
X-Voxio-Delivery-Id Stable unique delivery ID. The same value is reused when Voxio retries the same queued delivery, so receivers can deduplicate safely.
X-Voxio-Signature Legacy body-only HMAC signature kept temporarily for backward compatibility. New integrations should use Signature-V2.
X-Voxio-Client-Id Optional legacy API credential identifier when one exists. Do not use it instead of HMAC verification.
X-Voxio-Client-Secret Deprecated compatibility header. New receivers must not depend on it; use Relay Secret HMAC verification instead.

Signature Formula

To verify payload integrity, compute Signature-V2 on your server with the Relay Secret configured in Voxio, then compare it using a timing-safe comparison. Validate the timestamp before processing and optionally deduplicate by X-Voxio-Delivery-Id. Do not use the External API Client Secret as the HMAC key.

text
X-Voxio-Signature-V2 = sha256=HMAC_SHA256(X-Voxio-Timestamp + "." + raw_request_body, relay_secret)

Sample Payload

json
{
  "event": "incoming_message",
  "occurred_at": "2026-09-18T20:30:12+07:00",
  "user_id": 11,
  "channel": "instagram",
  "channel_id": "instagram:61",
  "social_channel_account_id": 61,
  "conversation_id": 101,
  "contact_id": 55,
  "message_id": "provider-message-id",
  "internal_message_id": 9001,
  "message_type": "text",
  "text": "Hello"
}

Recommended Receiver Behavior

Return HTTP 2xx as soon as the payload has been accepted. Process slow tasks asynchronously in your own system. Voxio treats non-2xx responses or timeouts as failed delivery attempts.

Always verify Signature-V2 against the exact raw body and reject stale timestamps before trusting the payload. Keep the Relay Secret private. X-Voxio-Delivery-Id can be stored to deduplicate retries.