Webhooks
Showdini can POST to an endpoint you control when something happens on a demo. Configure webhooks per demo show in the Showdini app, under Settings → Integrations:
https://app.showdini.ai/p/YOUR_DEMO_ID/demo-settings/integrations
Events
| Event | Fires when |
|---|---|
lead.report_generated | A visitor finished a demo and their lead report has been generated. |
test | You send a test request from the app. Never fires on its own. |
Request
Showdini sends a POST with a JSON body.
| Header | Value |
|---|---|
Content-Type | application/json |
X-Webhook-Event | The event name, e.g. lead.report_generated |
Authorization | Bearer <your secret> — only if you configured a secret |
Your endpoint must be a public http or https URL. Private, loopback, and
link-local addresses are rejected, and redirects are not followed.
Delivery
- Timeout: 10 seconds per attempt.
- Retries: up to 2 attempts total, 5 seconds apart.
- Success: any
2xxresponse. Anything else counts as a failure and is retried.
Respond 200 as soon as you have accepted the payload and do the real work
asynchronously — slow endpoints get retried and then given up on.
Every delivery, successful or not, is recorded in the delivery log in the app so you can see the status code and response body we received.
Payload
Every event uses the same envelope:
{
"event": "lead.report_generated",
"demoShowId": "6712f0a1b2c3d4e5f6a7b8c9",
"timestamp": "2026-08-31T10:04:12.512Z",
"data": { }
}
lead.report_generated
data contains the lead and the session it came from:
| Field | Type | Description |
|---|---|---|
sessionId | string | The demo session. |
reportId | string | The generated report. |
demoShowId | string | The demo show the session belongs to. |
demoShowName | string | Human-readable demo name. |
email | string | null | Lead email, if the visitor gave one. |
customFields | object | Values collected by your demo's form fields. |
metadata | object | Your own key/value data. See Metadata. |
leadScore | number | Lead score for the session. |
insights | string | null | AI-generated summary of the conversation. |
timeline | array | Ordered events that happened during the session. |
interactedCta | boolean | Whether the visitor interacted with the CTA. |
interactedCtaAt | string | null | ISO timestamp of that interaction. |
startedAt / endedAt | string | ISO timestamps bounding the session. |
playedClips | array | Which demo clips were played. |
language | string | null | Language the demo ran in. |
detectedLanguages | string[] | Languages detected during the conversation. |
country / city / timezone | string | null | Approximate visitor location. |
userAgent | string | null | Visitor's browser user agent. |
embedded | boolean | Whether the demo ran embedded in another site. |
leadUrl | string | Deep link to the lead in the Showdini app. |
transcript | array | Full conversation transcript. |
Sending a test request
Before going live, send a test delivery to your endpoint from the Integrations page: open the menu on a webhook and choose Test webhook.
Pick any event the webhook is subscribed to and Showdini sends a realistic
payload for it, or pick the synthetic test event for a plain ping:
{
"event": "test",
"demoShowId": "6712f0a1b2c3d4e5f6a7b8c9",
"timestamp": "2026-08-31T10:04:12.512Z",
"data": { "message": "This is a test webhook delivery from Showdini." }
}
The modal previews the exact body before you send, then shows the status code and response body your endpoint returned — so you can confirm the URL, the bearer token, and your handler in one round trip. The webhook has to be enabled first; a disabled webhook cannot be tested.
Verifying a request
If you set a secret on the webhook, Showdini sends it as a bearer token. Compare it against your stored value before trusting the payload:
app.post("/showdini-webhook", express.json(), (req, res) => {
const token = req.get("authorization")?.replace("Bearer ", "");
if (token !== process.env.SHOWDINI_WEBHOOK_SECRET) {
return res.sendStatus(401);
}
res.sendStatus(200); // acknowledge first
handleLead(req.body).catch(console.error); // then do the work
});
The secret is a shared bearer token, not an HMAC signature of the body. Always serve your webhook endpoint over HTTPS so the token is not sent in the clear.