seny

Webhooks

Seny emits webhooks whenever something interesting happens on a widget — a new conversation starts, a tool is called, a visitor fills out a form, a call ends. Use them to pipe conversation data into your CRM, notify Slack, trigger follow-up emails, or build your own analytics on top.

Two kinds of webhooks

There are two distinct webhook patterns in Seny. Don't mix them up:

  • Tool webhooks— you define a custom tool and the agent calls your backend mid-conversation. See the Tools page for those.
  • Event webhooks— Seny POSTs to your URL when specific lifecycle events happen. That's what this page is about.

Configuring an endpoint

Settings → Webhooks → Add 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.

Events

conversation.started

Fired as soon as a visitor opens a conversation and the agent says its first word. Useful for real-time dashboards.

conversation.ended

Fired when the session closes. Payload includes the full transcript, duration, credits charged, tool calls made, and any data extracted from the conversation (see the Conversations reference for the schema).

form.submitted

Fired when the agent successfully submits a form on behalf of a visitor. Payload includes the form selector, the field values, and a reference to the conversation that triggered the submission.

criteria.evaluated

Fired after an agent call, once Seny has evaluated the conversation against the acceptance criteria you defined on the Goals tab. Use this to route calls that passed (or failed) certain criteria into different downstream workflows.

Payload shape

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

{
  "id": "evt_1abc",
  "event": "conversation.ended",
  "widget_id": "wgt_abc123",
  "created_at": "2026-04-10T12:34:56Z",
  "data": {
    "conversation_id": "conv_xyz",
    "duration_seconds": 127,
    "credits_charged": 127,
    "page_url": "https://example.com/pricing",
    "outcome": "completed",
    "transcript": [
      { "role": "agent", "text": "Hi! How can I help?" },
      { "role": "user",  "text": "Do you cater vegan options?" }
    ],
    "extracted": {
      "intent": "catering_inquiry",
      "event_date": "2026-06-15"
    }
  }
}

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"),
  );
}

Retries

Seny retries failed deliveries (any non-2xx response or timeout > 5s) with exponential backoff for up to 24 hours: 1 min, 5 min, 30 min, 2 hr, 6 hr, 24 hr. After that the event is marked undeliverable. Use the Webhooks dashboard to resend failed deliveries manually.

Idempotency

Every event has a stable id. Store the ones you've processed and skip duplicates — retries will reuse the same id.