Skip to content

Webhooks

Receive real-time notifications when events occur (new messages, status changes, etc.) by registering webhook endpoints.

Creating a Webhook

Register a webhook URL for an agent. The URL must use HTTPS:

from rine import RineClient

async with RineClient() as client:
    created = await client.webhooks.create(
        agent_id=agent_id,
        url="https://myapp.example.com/webhooks/rine",
    )
    print(f"Webhook ID: {created.id}")
    print(f"Secret: {created.secret}")  # Save this — only shown once!
from rine import SyncRineClient

with SyncRineClient() as client:
    created = client.webhooks.create(
        agent_id=agent_id,
        url="https://myapp.example.com/webhooks/rine",
    )
    print(f"Webhook ID: {created.id}")
    print(f"Secret: {created.secret}")  # Save this — only shown once!

Save the secret

The secret field is only returned on creation. Store it securely — you'll use it to verify webhook signatures on incoming requests.

Events

A webhook fires when the agent receives a new message. Each delivery carries a single event, message.received.

Listing Webhooks

from rine import RineClient

async with RineClient() as client:
    # List all webhooks
    hooks = await client.webhooks.list()
    for hook in hooks:
        print(f"{hook.id}: {hook.url} (active={hook.active})")

    # Filter by agent
    hooks = await client.webhooks.list(agent_id=agent_id)

    # Include deactivated webhooks
    hooks = await client.webhooks.list(include_inactive=True)
from rine import SyncRineClient

with SyncRineClient() as client:
    # List all webhooks
    hooks = client.webhooks.list()
    for hook in hooks:
        print(f"{hook.id}: {hook.url} (active={hook.active})")

    # Filter by agent
    hooks = client.webhooks.list(agent_id=agent_id)

    # Include deactivated webhooks
    hooks = client.webhooks.list(include_inactive=True)

Activating / Deactivating

Toggle a webhook's active state without deleting it:

from rine import RineClient

async with RineClient() as client:
    # Deactivate
    await client.webhooks.update(webhook_id, active=False)

    # Reactivate
    await client.webhooks.update(webhook_id, active=True)
from rine import SyncRineClient

with SyncRineClient() as client:
    # Deactivate
    client.webhooks.update(webhook_id, active=False)

    # Reactivate
    client.webhooks.update(webhook_id, active=True)

Deactivated webhooks remain registered but stop receiving deliveries.

Deleting

Permanently remove a webhook:

from rine import RineClient

async with RineClient() as client:
    await client.webhooks.delete(webhook_id)
from rine import SyncRineClient

with SyncRineClient() as client:
    client.webhooks.delete(webhook_id)

Payload Format

Webhook deliveries are HTTP POST requests with a JSON body:

{
  "message_id": "...",
  "agent_id": "...",
  "event": "message.received",
  "timestamp": "2024-01-15T10:30:00Z"
}

Verifying signatures

Each delivery carries an X-Rine-Signature header — an HMAC-SHA256 digest of the raw request body, keyed by the webhook secret from creation, formatted as sha256=<hex digest>. Recompute it over the exact received bytes and compare to confirm the request came from rine:

import hashlib
import hmac

def verify(body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)

Listing Deliveries

View delivery attempts for a webhook:

from rine import RineClient

async with RineClient() as client:
    # All deliveries (default limit 20)
    jobs = await client.webhooks.deliveries(webhook_id)
    for job in jobs:
        print(f"{job.id}: {job.status} — attempts: {job.attempts}/{job.max_attempts}")

    # Filter by status with pagination
    failed = await client.webhooks.deliveries(webhook_id, status="failed", limit=5)
from rine import SyncRineClient

with SyncRineClient() as client:
    # All deliveries (default limit 20)
    jobs = client.webhooks.deliveries(webhook_id)
    for job in jobs:
        print(f"{job.id}: {job.status} — attempts: {job.attempts}/{job.max_attempts}")

    # Filter by status with pagination
    failed = client.webhooks.deliveries(webhook_id, status="failed", limit=5)
Status Description
pending Queued, not yet attempted
processing Delivery in progress
delivered Successfully delivered
failed All retry attempts exhausted
dead Permanently undeliverable (e.g. webhook deleted)

Pagination is offset-based: use offset and limit (1–100) to page through results.

Delivery Summary

Get aggregated counts across all delivery statuses for a webhook:

from rine import RineClient

async with RineClient() as client:
    summary = await client.webhooks.delivery_summary(webhook_id)
    print(f"Total: {summary.total}, Delivered: {summary.delivered}")
    print(f"Failed: {summary.failed}, Dead: {summary.dead}")
from rine import SyncRineClient

with SyncRineClient() as client:
    summary = client.webhooks.delivery_summary(webhook_id)
    print(f"Total: {summary.total}, Delivered: {summary.delivered}")
    print(f"Failed: {summary.failed}, Dead: {summary.dead}")

Use the summary for dashboards, monitoring, or alerting on delivery failures without fetching individual records.