Webhooks

Seny POSTs to your URL when lifecycle events happen — a conversation starts, ends, or gets evaluated; an agent is activated, paused, or finishes a crawl. Use them to pipe conversation data into your CRM, notify Slack, trigger follow-up emails, or build your own analytics on top.

Not the only webhook in Seny

Three related features share the name. Don't mix them up:

  • Event webhooks— Seny POSTs to your URL when specific lifecycle events happen. That's what this page is about.
  • Custom tools— you define a tool and the agent calls your backend mid-conversation. See the Tools page for those.
  • Post-call webhooks— a separate feature that delivers the full transcript (and optionally the call recording) after each call. It's configured in Settings → Post-call webhooks (a separate Settings tab from Settings → Webhooks), where each hook can be scoped to one agent or all agents — not on the agent editor itself.

Configuring an endpoint

Settings → Webhooks → New webhook endpoint (then Create endpoint). Paste your URL, select which events you want to subscribe to, and save. Seny generates a signing secret you'll use to verify payloads. Subscribe an endpoint to individual event types — wildcard/"*" subscriptions are not currently supported through the dashboard or the API, which both strip any value that isn't one of the six named event types. Endpoints can be managed programmatically too — see the Webhooks API.

Events

conversation.started

Fired when a visitor opens a conversation — web voice, web text, or inbound phone. Useful for real-time dashboards.

conversation.ended

Fired when the conversation fully closes (hang-up, idle timeout, or an explicit end). The payload carries a summary of the conversation — id, page URL, duration, outcome, extracted data, and criteria results — not the full transcript. If you need transcripts or recordings pushed to you, use per-agent post-call webhooks instead.

conversation.evaluated

Fired once Seny has evaluated the conversation against the acceptance criteria you defined on the Outcomes tab (Configure → Outcomes). Use this to route conversations that passed (or failed) certain criteria into different downstream workflows.

widget.activated / widget.paused

Fired when an agent moves from draft to active (the embed starts serving real conversations) or gets paused (the embed stops accepting new conversations). The payload includes the previous status and who made the change.

widget.crawl_completed

Fired when a knowledge-base crawl finishes — success or failure — with page counts and timing, so you can track crawl freshness in your own systems.

Payload shape

Every delivery uses the same envelope: { event, created_at, data }. The data shape varies per event.

POST https://your-backend.example.com/seny-webhook
Content-Type: application/json
X-Seny-Signature: sha256=abc123...
X-Seny-Event: conversation.ended
User-Agent: Seny-Webhooks/1.0

{
  "event": "conversation.ended",
  "created_at": "2026-05-01T19:46:12.000Z",
  "data": {
    "conversation": {
      "id": "1f3c0b8e-9b21-4e4e-9f4a-7c8d0a2b3a91",
      "widget_id": "76c26c0a-7ce3-4c54-b4c1-ba693c1ca154",
      "page_url": "https://example.com/pricing",
      "duration_seconds": 187,
      "outcome": "completed",
      "extracted_data": {
        "visitor_name": "Casey Lee",
        "visitor_email": "casey@example.com"
      },
      "criteria_results": [
        { "name": "Visitor identified", "pass": true, "reason": "Email captured" }
      ]
    },
    "widget": {
      "id": "76c26c0a-7ce3-4c54-b4c1-ba693c1ca154",
      "name": "Acme bot"
    }
  }
}

The dashboard shows an example payload for every event type, so you can see exactly what to expect before wiring up a handler.

Verifying signatures

Every event is signed with HMAC-SHA256 using your endpoint secret. Verify before trusting the payload.

import crypto from "node:crypto";

export function verifyWebhook(rawBody, signatureHeader, secret) {
  const [scheme, sig] = signatureHeader.split("=");
  if (scheme !== "sha256") return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(sig, "hex"),
    Buffer.from(expected, "hex"),
  );
}

Delivery

Each event is delivered once, with a 10-second timeout. There are no automatic retries and no manual resend — if your endpoint is down or slow, that delivery is missed. So respond with a 2xx as fast as you can: acknowledge immediately, then process the payload asynchronously. Every attempt (status code, response body, duration) is logged to the deliveries view so you can see exactly what your server returned, and you can send a test event any time to check your receiver.