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.