Webhooks
Register webhook endpoints to receive lifecycle and post-call intelligence events from Rymi.
Register Webhook
POST /v1/webhooksRequest Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | Yes | — | HTTPS endpoint to receive events. Must pass URL safety rules |
events | string[] | Yes | — | Events to subscribe to (see Available Events) |
secret | string | Yes | — | HMAC signing secret used to verify deliveries. Min 16 characters |
alert_email | string | No | — | Optional email address to notify when delivery fails after retries. Throttled to one alert per hour per endpoint |
Available Events
The dashboard's webhook manager mirrors this catalog under Settings → Webhooks. New event types are added without breaking existing subscribers — handlers should ignore unknown payload types.
Call events
| Event | Description |
|---|---|
call.completed | A call ended with transcript and metadata. Fires together with call.intelligence.ready once post-call processing finishes |
call.intelligence.ready | Post-call analysis (summary, extraction, evaluation) is ready |
Agent events
| Event | Description |
|---|---|
agent.published | An agent was published to a live version |
agent.failed | An agent runtime hit a fatal error |
agent.number_assigned | A phone number was attached to an agent |
Billing events
| Event | Description |
|---|---|
billing.topup_confirmed | A wallet top-up was captured |
billing.payment_failed | A payment attempt failed |
Auth & system events
| Event | Description |
|---|---|
auth.welcome | First successful sign-up |
system.api_key_created | A new secret API key was issued |
system.api_key_revoked | An API key was revoked |
Reserved event names
These names are accepted at registration time but are not emitted yet. Don't build flows that depend on them; watch the changelog for when they go live.
call.started · call.failed · batch.completed · billing.low_balance · billing.balance_depleted · custom.alert
Example Request
curl -X POST https://api.rymi.live/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/rymi",
"events": ["call.intelligence.ready", "call.failed", "batch.completed"],
"secret": "whsec_your_webhook_secret_here"
}'Response 201
{
"id": "wh_123",
"url": "https://example.com/webhooks/rymi",
"events": ["call.intelligence.ready", "call.failed", "batch.completed"],
"created_at": "2026-03-01T10:00:00Z"
}Errors
| Status | Meaning |
|---|---|
400 | Missing or invalid url, events, or secret |
401 | Missing or invalid API key |
Payload Format
When an event fires, Rymi sends a POST request to your registered URL with the following JSON body:
call.intelligence.ready Payload
{
"type": "call.intelligence.ready",
"event": "call.intelligence.ready",
"timestamp": "2026-03-01T10:05:00Z",
"data": {
"call_id": "call_abc123",
"status": "completed",
"duration": 145,
"cost": 0.21,
"intelligence_status": "completed",
"transcript": {
"text": "Caller: Hello\nAgent: Hi there",
"segments": [
{
"speaker": "user",
"text": "Hello",
"sequence": 0,
"started_at_ms": 0,
"ended_at_ms": 900
}
]
},
"summary": "Caller asked for support and the agent resolved the issue.",
"sentiment": "neutral",
"structured_data": {
"customer_intent": "support"
},
"evaluation": {
"passed": true,
"score": 1,
"reasoning": "The issue was resolved."
},
"errors": []
}
}Notification-event Payload
Agent, billing, auth, and system events are delivered with a lighter notification-shaped data object:
{
"type": "agent.number_assigned",
"event": "agent.number_assigned",
"timestamp": "2026-03-01T10:01:00Z",
"data": {
"notification_id": "ntf_123",
"tenant_id": "ten_456",
"event_category": "agent.number_assigned",
"title": "Number attached",
"message": "+15551234567 now routes to Priya - Sales Specialist",
"metadata": { "number": "+15551234567" }
}
}Payload Fields
| Field | Type | Description |
|---|---|---|
type | string | Event name (same value as event) |
event | string | Event name |
timestamp | string | ISO 8601 timestamp of when the event was emitted |
data | object | Event-specific payload |
Delivery Behavior
- Timeout: Rymi waits up to 3 seconds for your endpoint to respond with a
2xxstatus code. Acknowledge fast and process asynchronously — don't do heavy work before responding. - Retries: Each event is attempted up to 5 times with exponential backoff (roughly 1s, 2s, 4s, 8s between attempts).
- Failure alerts: When all retries are exhausted, Rymi emails the
alert_emailregistered with the webhook (if set). Throttled to one alert per hour per endpoint. - Ordering: Events are delivered in approximate order but are not guaranteed to arrive sequentially. Use
timestampandcall_idfor deduplication. - Idempotency: Your webhook handler should be idempotent — the same event may be delivered more than once.
Update Webhook
PATCH /v1/webhooks/:idUpdate an existing webhook's URL, subscribed events, or signing secret. Only provided fields are changed.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Webhook ID |
Request Body
All fields are optional. Provide only the fields you want to change.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | No | New HTTPS endpoint URL |
events | string[] | No | Updated event subscriptions |
secret | string | No | New HMAC signing secret (min 16 characters) |
alert_email | string | null | No | Replace the failure-alert email. Pass null or "" to clear it |
Example — Change URL
curl -X PATCH https://api.rymi.live/v1/webhooks/wh_123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://new-endpoint.example.com/webhooks/rymi"
}'Example — Update Subscribed Events
curl -X PATCH https://api.rymi.live/v1/webhooks/wh_123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": ["call.intelligence.ready", "call.failed", "call.started"]
}'Response 200
{
"status": "updated",
"id": "wh_123"
}Errors
| Status | Meaning |
|---|---|
400 | Invalid url (not HTTPS, private network, etc.) or empty body |
401 | Missing or invalid API key |
404 | Webhook not found |
List Webhooks
GET /v1/webhooksQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Max records to return |
offset | integer | 0 | Records to skip |
Example Request
curl "https://api.rymi.live/v1/webhooks?limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"Response 200
{
"webhooks": [
{
"id": "wh_123",
"url": "https://example.com/webhooks/rymi",
"events": ["call.intelligence.ready"],
"created_at": "2026-03-01T10:00:00Z"
}
],
"total": 1,
"offset": 0,
"limit": 10
}Errors
| Status | Meaning |
|---|---|
401 | Missing or invalid API key |
Delete Webhook
DELETE /v1/webhooks/:idPath Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Webhook ID |
Example Request
curl -X DELETE https://api.rymi.live/v1/webhooks/wh_123 \
-H "Authorization: Bearer YOUR_API_KEY"Response 200
{
"status": "deleted",
"id": "wh_123"
}Errors
| Status | Meaning |
|---|---|
401 | Missing or invalid API key |
404 | Webhook not found |
Signature Verification
Every webhook delivery includes three headers for verification:
| Header | Description |
|---|---|
X-Rymi-Event | Event name (e.g., call.intelligence.ready) |
X-Rymi-Timestamp | Unix timestamp (milliseconds) used in the signature payload |
X-Rymi-Signature | HMAC-SHA256 hex digest of {timestamp}.{raw_body} |
Verification Steps
- Read the raw request body exactly as received (do not parse JSON first).
- Concatenate
X-Rymi-Timestamp, a literal., and the raw body string. - Compute an HMAC-SHA256 digest using your webhook
secretas the key. - Compare the result to
X-Rymi-Signatureusing a timing-safe comparison.
Example (Node.js)
const crypto = require('crypto');
function verifyWebhook(rawBody, signature, timestamp, secret) {
const payload = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
throw new Error('Invalid webhook signature');
}
}Or use the built-in verifier in @rymi/node:
rymi.webhooks.verifySignature(rawBody, signature, timestamp, secret);TIP
HTTP header names are case-insensitive. Frameworks commonly expose these as lowercase keys: x-rymi-signature, x-rymi-timestamp, x-rymi-event.
URL Safety Rules
Rymi rejects webhook URLs that look unsafe before saving them. Validation runs on POST /v1/webhooks and on every PATCH /v1/webhooks/:id that updates url. A 400 is returned with a human-readable error describing what to fix.
- Production webhook URLs must use
https://. Plainhttp://is rejected outside the test environment. - Hostnames
localhost,metadata.google.internal, and the EC2 metadata IP169.254.169.254are blocked. .localand.internalhostnames are rejected — they aren't reachable from the public internet. Use a tunnel (ngrok, Cloudflare Tunnel) for local testing.- Hostnames that resolve to a private IPv4 (
10.0.0.0/8,127.0.0.0/8,169.254.0.0/16,172.16.0.0/12,192.168.0.0/16) or IPv6 unique-local / link-local range are rejected. - URLs that include
username:password@credentials are rejected. Use a header on your end instead. - Hostnames that fail DNS resolution are rejected with the resolution error.

