docs / webhooks

Webhooks

krabs delivers signed, retried, at-least-once webhooks for every event your agents care about.

Subscribing

From the dashboard, or CLI:

krabs webhooks create \
  --url https://yourapp.com/krabs \
  --events "deal.*,contact.upsert"

Output:

webhook_id: whk_01HGZ9X4QY8M2N7P3R5T6V8W
url:        https://yourapp.com/krabs
events:     deal.*, contact.upsert
secret:     whsec_2v8x4n6q1jpz9w8x1y0c5b6d4f8g
            ↑ copy now, you will not see this again

The signing secret is shown once. krabs stores only a hash and uses the plaintext to sign outgoing payloads.

Event types

Patterns support * as a single-segment wildcard. deal.* matches deal.created and deal.stage_changed but not deal.line.added; use deal.** for the latter.

eventfires when
contact.createda new contact row is written
contact.updatedany field on a contact changes
deal.createda new deal is opened
deal.stage_changeda deal moves between pipeline stages
task.completeda task transitions to done
interaction.receivedan inbound message lands on any channel

Payload shape

Every delivery is one JSON object. The data field carries the full record after the change; the actor field names the agent or human that caused it.

{
  "id": "evt_01HG7Z4X9Q8M2N7P3R5T6V8W",
  "type": "deal.stage_changed",
  "created_at": "2026-05-16T14:22:08Z",
  "data": {
    "id": "dl_01HG…",
    "name": "Acme Q3 expansion",
    "stage": "negotiation",
    "previous_stage": "proposal",
    "amount": 48000,
    "currency": "USD"
  },
  "actor": {
    "kind": "agent",
    "id": "agent_drafts",
    "label": "drafts"
  },
  "run_id": "run_01HG7Z4X9Q8M2N7P3R5T6V8W"
}

Signing & verification

krabssigns every payload with HMAC-SHA256 using the webhook’s secret. The signature lands in X-Krabs-Signature as t=<timestamp>,v1=<hex>. Verify in Node.js:

import crypto from "node:crypto";

export function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("=") as [string, string])
  );
  const t = parts.t;
  const sig = parts.v1;
  if (!t || !sig) return false;

  const signed = `${t}.${rawBody}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signed)
    .digest("hex");

  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(sig, "hex");
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

Reject the request if the signature does not verify or the timestamp is more than five minutes off wall-clock.

Retries

Failed deliveries retry with exponential backoff: +30s, +5m, +25m. Three attempts total, capped at thirty minutes. After the third failure the delivery is marked failed and surfaced in the dashboard.

respond within 10s
Webhook endpoints must respond with a 2xx status within 10 seconds or krabs counts the delivery as a failure and schedules a retry. Acknowledge first, process asynchronously.

Replay

Every delivery is logged. Replay any delivery — failed or successful — from the dashboard or CLI:

krabs webhooks replay dlv_01HG7Z4X9Q8M2N7P3R5T6V8W

Replay sends the original payload with the original signature, plus a new X-Krabs-Delivery-Id header so your handler can dedupe. Replayed events do not count against retry budgets.

Runs & SSE → The contract →

Edit this page on GitHub →last updated 2026-05-16 · v0.4.3