Notifications

Receive a webhook when an outcome confirms or is missed.

Notifications send a POST request to a URL you control each time an outcome confirms or is missed. You set up a rule in the dashboard, pick which agent to watch, and witn delivers a signed payload.

Setting up a rule

Notification rule configuration
  1. Open Notifications in the sidebar.
  2. Click New rule.
  3. Choose a source: Outcome confirmed or Outcome missed.
  4. Select the agent to watch.
  5. Enter your endpoint URL.
  6. Copy the signing secret and store it as an environment variable.
  7. Enable the rule.

Use Send test on the rule page to confirm your endpoint receives and verifies the payload correctly.

Webhook payload

{
  "id": "a3f1c2b4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2",
  "type": "outcome_succeed",
  "createdAt": "2026-07-01T14:00:00.000Z",
  "data": {
    "rule": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Support agent success"
    },
    "subjectKey": "support:ticket:1001",
    "facts": {
      "outcomeKey": "support:ticket:1001",
      "customerKey": "acme",
      "agentKey": "support",
      "agentName": "Support"
    }
  }
}
FieldDescription
idUnique event ID. Stable across retries. Deduplicate on it.
typeoutcome_succeed when confirmed, outcome_failed when missed.
data.subjectKeyThe outcome key you sent when opening the outcome.
data.facts.outcomeKeySame as subjectKey.
data.facts.customerKeyThe customer key on the outcome.
data.facts.agentKeyThe agent key on the outcome.

Verifying signatures

Every delivery is signed. Verify the signature before processing the payload.

import crypto from "node:crypto";

function verifyWebhook(req: Request, body: string, secret: string): unknown {
  const msgId = req.headers.get("svix-id")!;
  const timestamp = req.headers.get("svix-timestamp")!;
  const signatures = req.headers.get("svix-signature")!;

  const ts = parseInt(timestamp, 10);
  if (Math.abs(Date.now() / 1000 - ts) > 300) {
    throw new Error("Timestamp too old");
  }

  const secretBytes = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const computed = crypto
    .createHmac("sha256", secretBytes)
    .update(`${msgId}.${timestamp}.${body}`)
    .digest("base64");

  const valid = signatures.split(" ").some((s) => s === `v1,${computed}`);
  if (!valid) throw new Error("Invalid signature");

  return JSON.parse(body);
}

export async function POST(req: Request) {
  const body = await req.text();
  const event = verifyWebhook(req, body, process.env.WITN_WEBHOOK_SECRET!);
  // handle event
}

Reject requests where the timestamp is more than 5 minutes old to prevent replay attacks.

Rotating the signing secret

Click Rotate secret on the rule page. The old secret stops working immediately. Update your environment variable before rotating.

On this page