Message callbacks
Character messages for API conversations are delivered by POST to the current workspace's verified, active MESSAGE callback. A missing callback fails delivery; configuration from another workspace is never used.
The console's interactive test uses the platform inbox and does not invoke this message callback. Use the test workspace's public API to test real delivery. Synchronous business tools can still execute real callbacks during console testing.
Configuration and verification
Configure a message callback and signing secret in Open API settings. Use HTTPS; redirects are not followed and private-network addresses cannot be public callbacks. Run verification after saving: verify the signature of OC-Event-Type: endpoint.verify and return 2xx. Changing the address requires verification again.
Callback signatures
| Header | Meaning |
|---|---|
OC-App-Key | Workspace AppKey, used to select the matching callback secret |
OC-Event-Id | Stable event ID; actionId for message callbacks |
OC-Event-Type | chat.message, or endpoint.verify for address verification |
OC-Timestamp | Unix milliseconds, regenerated on each attempt |
OC-Signature | v1= followed by lowercase hexadecimal HMAC-SHA256; rotation may supply multiple comma-separated signatures |
Idempotency-Key | Same as OC-Event-Id |
Sign these four UTF-8 lines with the secret for this callback purpose, not AppSecret. Do not append a trailing newline.
OC-WEBHOOK-HMAC-SHA256
{OC-Event-Id}
{OC-Timestamp}
{SHA-256 of raw request body, lowercase hexadecimal}Verify raw bytes before parsing JSON. Use a timestamp tolerance of approximately five minutes in either direction, and constant-time comparison. During rotation, any signature matching a valid secret is sufficient. The Node.js signing helper provides verifyWebhook.
Text messages
{"actionId":"123","conversationId":"456","messageId":"789","type":"text","text":"Hello, how was your day?","context":{"workspaceId":"1","conversationId":"456","personaId":"11","personaUid":"anchor-001","userId":"u-1001"}}personaUid and userId are your identifiers; the others belong to OmniChat. An unbound personaUid may be null. messageId identifies the message; actionId identifies delivery and is distinct from a tool's callId.
Asset messages
When asset tools are enabled and authorized, type is asset with an asset object instead of text:
{"actionId":"124","conversationId":"456","messageId":"790","type":"asset","asset":{"assetId":"42","kind":"image","name":"Image","url":"https://cdn.example.com/image.png","caption":"Take a look"},"context":{"workspaceId":"1","conversationId":"456","personaId":"11","personaUid":"anchor-001","userId":"u-1001"}}The example URL is illustrative. Render by kind; do not assume permanent URL validity. Asset output does not imply support for user media input.
Action messages
type: "action" carries an action object; see tool execution. EXECUTE requires execution and a business result. NOTIFY only displays a previously executed result and must not execute it again.
Responses and retries
| Message | Successful response |
|---|---|
Text, asset, NOTIFY | HTTP 2xx after reliable acceptance; body may be empty |
EXECUTE | HTTP 2xx with {"status":"SUCCEEDED","data":{}}; use FAILED for business failure or UNKNOWN for uncertainty |
Tool result JSON does not use the platform API's {code,message,data} envelope.
- Deduplicate by
actionId, returning the saved result on retries. Also deduplicate business execution byaction.callId. - Delivery is ordered within a conversation. Later messages wait while an earlier message retries.
- 4xx other than 408/429 is a terminal rejection. Timeouts, 5xx, and other uncertain results receive at most 3 attempts, with 10-second and 30-second retry delays and the same
actionId. - Terminal uncertainty stops automatic retries and unblocks the conversation. Unknown does not mean unexecuted. Unknown ordinary messages are excluded from confirmed context; unknown actions add a reconciliation notice.
- For
EXECUTE, a 2xx without a valid business status is also unknown. Return complete JSON within 8,000 UTF-8 bytes. Respond within 10 seconds; there is no separate completion-report endpoint for long-running tasks.
There is currently no public manual reconciliation or redelivery API.