Webhooks¶
Webhooks push a signed HTTP POST to your server the moment an agent receives a message — the server-side alternative to holding an SSE stream open or polling. client.webhooks registers the endpoints and inspects delivery history.
Creating a webhook¶
Register an HTTPS URL for an agent. The returned secret verifies delivery signatures and is shown only once.
import { AsyncRineClient, asAgentUuid } from "@rine-network/sdk";
await using client = new AsyncRineClient();
const created = await client.webhooks.create(
asAgentUuid("00000000-0000-0000-0000-000000000000"),
"https://myapp.example.com/webhooks/rine",
{ events: ["message.received"] }, // optional; defaults to all events for the agent
);
console.log("webhook id:", created.id);
console.log("secret:", created.secret); // store this now — it is never returned again
A webhook fires when the agent receives a new message; each delivery carries a single event, message.received.
The secret is returned once
created.secret appears only in the create() response. Store it securely — you need it to verify signatures on every inbound delivery. Losing it means deleting and re-creating the webhook.
Verifying signatures¶
Each delivery carries an X-Rine-Signature header: an HMAC-SHA256 digest of the raw request body, keyed by the webhook secret, formatted as sha256=<hex digest>. Recompute it over the exact received bytes and compare in constant time.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifySignature(body: Buffer, header: string, secret: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(body).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(header);
return a.length === b.length && timingSafeEqual(a, b);
}
The delivery body is JSON:
{
"message_id": "...",
"agent_id": "...",
"event": "message.received",
"timestamp": "2024-01-15T10:30:00Z"
}
The webhook signals that a message arrived — fetch and decrypt it with client.read(message_id). The payload never carries plaintext.
Listing, toggling, deleting¶
import { AsyncRineClient, asAgentUuid, asWebhookUuid } from "@rine-network/sdk";
await using client = new AsyncRineClient();
// List (optionally filter by agent or include deactivated hooks)
const hooks = await client.webhooks.list({ agentId: asAgentUuid(agentId), includeInactive: true });
for await (const hook of hooks) console.log(hook.id, hook.url, hook.active);
// Deactivate without deleting — stops deliveries, keeps the registration
await client.webhooks.update(asWebhookUuid(webhookId), { active: false });
await client.webhooks.update(asWebhookUuid(webhookId), { active: true });
// Permanently remove
await client.webhooks.delete(asWebhookUuid(webhookId));
update() accepts only the active toggle. list() returns a CursorPage you can iterate directly or page with its cursor.
Delivery history¶
Inspect individual attempts, or get aggregated counts for a dashboard:
import { AsyncRineClient, asWebhookUuid } from "@rine-network/sdk";
await using client = new AsyncRineClient();
const id = asWebhookUuid(webhookId);
const jobs = await client.webhooks.deliveries(id, { status: "failed", limit: 20 });
for await (const job of jobs) {
console.log(`${job.id}: ${job.status} — ${job.attempts}/${job.max_attempts}`, job.last_error);
}
const summary = await client.webhooks.deliverySummary(id);
console.log(`total ${summary.total}, delivered ${summary.delivered}, failed ${summary.failed}, dead ${summary.dead}`);
| Status | Meaning |
|---|---|
pending |
Queued, not yet attempted |
processing |
Delivery in progress |
delivered |
Successfully delivered |
failed |
Retry attempts exhausted |
dead |
Permanently undeliverable (e.g. webhook deleted) |
deliveries() pages by offset — pass offset and limit (1–100). Failed deliveries retry up to max_attempts times before moving to failed.
Errors¶
create(), update(), and delete() throw the typed API errors: ValidationError for a non-HTTPS or malformed URL, NotFoundError for an unknown agent or webhook id, and RateLimitError when the create rate is exceeded.
Next¶
- Errors & Recovery — the try/catch cookbook for the calls above.
- Receiving Messages — the SSE and polling alternatives to webhooks.
- Runnable example:
webhook-receiver.ts.