Skip to content

Webhooks

Register webhook endpoints to receive lifecycle and post-call intelligence events from Rymi.

Register Webhook

http
POST /v1/webhooks

Request Body

FieldTypeRequiredDefaultDescription
urlstringYesHTTPS endpoint to receive events. Must pass URL safety rules
eventsstring[]YesEvents to subscribe to (see Available Events)
secretstringYesHMAC signing secret used to verify deliveries. Min 16 characters
alert_emailstringNoOptional 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

EventDescription
call.completedA call ended with transcript and metadata. Fires together with call.intelligence.ready once post-call processing finishes
call.intelligence.readyPost-call analysis (summary, extraction, evaluation) is ready

Agent events

EventDescription
agent.publishedAn agent was published to a live version
agent.failedAn agent runtime hit a fatal error
agent.number_assignedA phone number was attached to an agent

Billing events

EventDescription
billing.topup_confirmedA wallet top-up was captured
billing.payment_failedA payment attempt failed

Auth & system events

EventDescription
auth.welcomeFirst successful sign-up
system.api_key_createdA new secret API key was issued
system.api_key_revokedAn 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

bash
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

json
{
  "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

StatusMeaning
400Missing or invalid url, events, or secret
401Missing 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

json
{
  "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:

json
{
  "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

FieldTypeDescription
typestringEvent name (same value as event)
eventstringEvent name
timestampstringISO 8601 timestamp of when the event was emitted
dataobjectEvent-specific payload

Delivery Behavior

  • Timeout: Rymi waits up to 3 seconds for your endpoint to respond with a 2xx status 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_email registered 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 timestamp and call_id for deduplication.
  • Idempotency: Your webhook handler should be idempotent — the same event may be delivered more than once.

Update Webhook

http
PATCH /v1/webhooks/:id

Update an existing webhook's URL, subscribed events, or signing secret. Only provided fields are changed.

Path Parameters

ParameterTypeDescription
idstringWebhook ID

Request Body

All fields are optional. Provide only the fields you want to change.

FieldTypeRequiredDescription
urlstringNoNew HTTPS endpoint URL
eventsstring[]NoUpdated event subscriptions
secretstringNoNew HMAC signing secret (min 16 characters)
alert_emailstring | nullNoReplace the failure-alert email. Pass null or "" to clear it

Example — Change URL

bash
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

bash
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

json
{
  "status": "updated",
  "id": "wh_123"
}

Errors

StatusMeaning
400Invalid url (not HTTPS, private network, etc.) or empty body
401Missing or invalid API key
404Webhook not found

List Webhooks

http
GET /v1/webhooks

Query Parameters

ParameterTypeDefaultDescription
limitinteger50Max records to return
offsetinteger0Records to skip

Example Request

bash
curl "https://api.rymi.live/v1/webhooks?limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200

json
{
  "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

StatusMeaning
401Missing or invalid API key

Delete Webhook

http
DELETE /v1/webhooks/:id

Path Parameters

ParameterTypeDescription
idstringWebhook ID

Example Request

bash
curl -X DELETE https://api.rymi.live/v1/webhooks/wh_123 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200

json
{
  "status": "deleted",
  "id": "wh_123"
}

Errors

StatusMeaning
401Missing or invalid API key
404Webhook not found

Signature Verification

Every webhook delivery includes three headers for verification:

HeaderDescription
X-Rymi-EventEvent name (e.g., call.intelligence.ready)
X-Rymi-TimestampUnix timestamp (milliseconds) used in the signature payload
X-Rymi-SignatureHMAC-SHA256 hex digest of {timestamp}.{raw_body}

Verification Steps

  1. Read the raw request body exactly as received (do not parse JSON first).
  2. Concatenate X-Rymi-Timestamp, a literal ., and the raw body string.
  3. Compute an HMAC-SHA256 digest using your webhook secret as the key.
  4. Compare the result to X-Rymi-Signature using a timing-safe comparison.

Example (Node.js)

javascript
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:

typescript
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://. Plain http:// is rejected outside the test environment.
  • Hostnames localhost, metadata.google.internal, and the EC2 metadata IP 169.254.169.254 are blocked.
  • .local and .internal hostnames 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.