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

- Open Notifications in the sidebar.
- Click New rule.
- Choose a source: Outcome confirmed or Outcome missed.
- Select the agent to watch.
- Enter your endpoint URL.
- Copy the signing secret and store it as an environment variable.
- 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"
}
}
}| Field | Description |
|---|---|
id | Unique event ID. Stable across retries. Deduplicate on it. |
type | outcome_succeed when confirmed, outcome_failed when missed. |
data.subjectKey | The outcome key you sent when opening the outcome. |
data.facts.outcomeKey | Same as subjectKey. |
data.facts.customerKey | The customer key on the outcome. |
data.facts.agentKey | The 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.