Skip to content

Webhooks

Rymi pushes real-time events to your backend as HTTP POST requests whenever critical call, agent, billing, and system milestones occur.

Events

EventTrigger
call.completedCall ended with transcript and metadata (fires after post-call processing)
call.intelligence.readyPost-call analysis (summary, extraction, evaluation) is ready
agent.publishedAgent was published to a live version
agent.failedAgent runtime hit a fatal error
agent.number_assignedPhone number was attached to an agent
billing.topup_confirmedWallet top-up was captured
billing.payment_failedPayment attempt failed
auth.welcomeFirst successful sign-up
system.api_key_createdSecret API key was issued
system.api_key_revokedAPI key was revoked

Some additional event names (call.started, call.failed, batch.completed, billing.low_balance, billing.balance_depleted, custom.alert) are reserved but not emitted yet — see the Webhooks API reference for the authoritative catalog and payload examples.

Payload Format

json
{
  "type": "call.intelligence.ready",
  "event": "call.intelligence.ready",
  "timestamp": "2026-04-01T12:30:00Z",
  "data": {
    "call_id": "call_abc123",
    "agent_id": "550e8400-...",
    "status": "completed",
    "duration": 145,
    "cost": 0.21,
    "summary": "The caller confirmed the appointment.",
    "sentiment": "positive"
  }
}

Signature Verification

Every outbound webhook includes X-Rymi-Signature, X-Rymi-Timestamp, and X-Rymi-Event headers to verify the payload originated from Rymi and wasn't tampered with. Header names are case-insensitive, so most Node frameworks expose them as lowercase keys such as x-rymi-signature.

The signature is an HMAC-SHA256 hash of {timestamp}.{raw_body} using your webhook secret.

Verification Steps

  1. Extract x-rymi-signature and x-rymi-timestamp from the request headers
  2. Build the signed payload: {x-rymi-timestamp}.{raw_request_body}
  3. Compute HMAC-SHA256(webhook_secret, signed_payload)
  4. Compare the computed hash against x-rymi-signature using constant-time comparison
  5. Optionally reject requests where the timestamp is older than 5 minutes (replay protection)

Node.js Example

Using the @rymi/node SDK:

typescript
import express from 'express';
import { Rymi } from '@rymi/node';

const app = express();
const rymi = new Rymi();

app.post('/webhook', express.text({ type: 'application/json' }), (req, res) => {
    const signature = req.headers['x-rymi-signature'] as string;
    const timestamp = req.headers['x-rymi-timestamp'] as string;
    const payload = req.body;
    
    try {
        rymi.webhooks.verifySignature(
            payload,
            signature,
            timestamp,
            process.env.RYMI_WEBHOOK_SECRET!
        );
        
        const event = JSON.parse(payload);
        
        switch (event.type) {
            case 'call.intelligence.ready':
                console.log('Intelligence ready:', event.data.call_id, event.data.summary);
                break;
        }
        
        res.status(200).send('OK');
    } catch (error) {
        console.error('Invalid webhook signature!', error);
        res.status(401).send('Unauthorized');
    }
});

Manual Verification (without SDK)

typescript
import crypto from 'crypto';

function verifyWebhook(rawBody: string, signature: string, timestamp: string, secret: string): boolean {
    const signedPayload = `${timestamp}.${rawBody}`;
    const expected = crypto
        .createHmac('sha256', secret)
        .update(signedPayload)
        .digest('hex');
    
    return crypto.timingSafeEqual(
        Buffer.from(expected, 'hex'),
        Buffer.from(signature, 'hex')
    );
}

Python Example

python
import hmac
import hashlib

def verify_webhook(raw_body: str, signature: str, timestamp: str, secret: str) -> bool:
    signed_payload = f"{timestamp}.{raw_body}"
    expected = hmac.new(
        secret.encode(), signed_payload.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Registering Webhooks

Configure your webhook URL via the Webhooks API or the dashboard settings.

Raw Body Required

You must verify the signature against the raw request body (the exact bytes sent over the wire), not a parsed-then-re-serialized JSON object. Frameworks like Express should use express.text() or express.raw() for the webhook route.

Retry Policy

Rymi waits up to 3 seconds for a 2xx response — acknowledge immediately and process the event asynchronously. Failed deliveries (non-2xx or timeout) are retried with exponential backoff up to 5 total attempts. If all attempts fail, the event is marked as failed in the dashboard event log, and the webhook's alert_email (if set) is notified at most once per hour.