Analytics Destinations
Destinations streams every Update Center, Demo Center, and subscriber interaction to a webhook you control, near real time. Pipe DemoBites engagement into your warehouse, CDP, CRM, or alerting, no polling, no export jobs.
How Destinations works
As visitors browse your Update Center and Demo Center, watch videos, click CTAs, search, and subscribe, DemoBites records those interactions. Destinations forwards them to your own HTTPS endpoints as signed JSON batches, typically within about a minute of the interaction. You choose exactly which events each endpoint receives from a 26-event catalog.
You’ll find it in Analytics, the Destinations control sits on its own, to the right of the view switcher. Only workspace admins can configure endpoints.
Adding an endpoint
Each workspace can have up to 3 endpoints, useful for separating, say, a production warehouse pipeline from a staging consumer or a Slack-alerting relay.
Give the endpoint a name you'll recognize in the delivery log, and enter the URL that will receive deliveries. HTTPS is required.
A signing secret (dbw_…) is shown once, at creation. Store it in your receiver's configuration, it's what you'll verify signatures with. You can rotate it later at any time.
Select which of the 26 events this endpoint should receive. You can change the selection whenever you like.
Use the test event button to fire a sample delivery and confirm your receiver accepts and verifies it before real traffic flows.
Choosing events
Subscription is per event, not all-or-nothing. The catalog holds 26 events in four groups, and the in-product picker documents when each one fires:
Page views, video engagement, CTAs, search, filters, calendar, and subscribe attempts.
Demo Center views, demo playback milestones, and CTA clicks.
The full subscriber lifecycle, from confirmed signup to bounce and suppression.
Visits from AI crawlers fetching your public pages.
Event reference
The full catalog, every event name and when it fires.
Update Center (14 events)
| Event | Fires when |
|---|---|
update_center_viewed | A visitor opens your Update Center home page. |
latest_page_viewed | A visitor opens the Latest page. |
release_page_viewed | A visitor opens a release page. |
update_page_viewed | A visitor opens an individual update page. |
update_video_started | A visitor starts playing a video on an Update Center page. |
update_video_progress | Fires once at each of 25%, 50%, and 75% of playback. |
update_video_completed | A visitor watches a video to the end. |
cta_clicked | A visitor clicks a call-to-action link on an Update Center page. Includes the destination host. |
search_performed | A visitor runs a search. Includes the query and the result count — zero-result searches included. |
search_result_clicked | A visitor clicks a search result. Includes the query, the result, and its rank. |
filter_applied | A visitor applies a filter to the updates feed. |
chapter_clicked | A visitor jumps to a chapter inside an update video. |
calendar_interacted | A visitor interacts with the release calendar. |
subscribe_form_submitted | A visitor submits the subscribe form. This is the attempt — the confirmed signup arrives as subscriber_created. |
Demo Center (5 events)
| Event | Fires when |
|---|---|
demo_center_viewed | A visitor opens your Demo Center page. |
demo_video_started | A visitor starts playing a demo video. |
demo_video_progress | Fires once at each of 25%, 50%, and 75% of demo playback. |
demo_video_completed | A visitor watches a demo video to the end. |
demo_cta_clicked | A visitor clicks a Demo Center call-to-action, including Play all and sign-up. |
Subscribers (6 events)
| Event | Fires when |
|---|---|
subscriber_created | A subscriber confirms their email — double opt-in complete. Includes consent details. |
subscriber_paused | A subscriber pauses their update emails. |
subscriber_resumed | A paused subscriber resumes their update emails. |
subscriber_unsubscribed | A subscriber unsubscribes — via their manage link or removed by a workspace owner. |
subscriber_bounced | An email to a subscriber permanently bounces; delivery to that address stops. |
subscriber_suppressed | A subscriber is suppressed after a spam complaint or a provider block; delivery to that address stops. |
AI crawlers (1 event)
| Event | Fires when |
|---|---|
crawler_visit | An AI crawler — GPTBot, ClaudeBot, PerplexityBot, and others — fetches one of your public pages. Includes which crawler. |
Subscriber events carry identity:they include the subscriber’s email address and their consent record (consent time and source surface), so treat endpoints receiving them as processors of personal data.
Verifying signatures
Every delivery is signed with your endpoint’s secret so your receiver can prove it came from DemoBites. The signature travels in one header:
X-DemoBites-Signature: t=<unix>,v1=<hex>t is the Unix timestamp of the delivery, andv1 is HMAC-SHA256(secret, t + “.” + rawBody) as hex. Compute the same HMAC over the raw request body exactly as received, then compare with a timing-safe comparison:
import { createHmac, timingSafeEqual } from "node:crypto";
// header: the X-DemoBites-Signature value, "t=<unix>,v1=<hex>"
// rawBody: the request body EXACTLY as received (don't re-serialize)
function verifySignature(header, rawBody, secret) {
const parts = Object.fromEntries(
header.split(",").map((pair) => pair.split("="))
);
const expected = createHmac("sha256", secret)
.update(parts.t + "." + rawBody)
.digest("hex");
return (
expected.length === parts.v1.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
);
}Verify against the raw body bytes, parsing and re-serializing the JSON first will change the string and break the signature. As an extra replay guard, you can reject deliveries whose t is older than your tolerance.
Payload envelope
Deliveries are HTTPS POSTs of a JSON object with a single events array, batching up to 50 events per delivery. Every event carries the same envelope:
| Field | Meaning |
|---|---|
event_id | Stable unique id for this event, the key to dedup on if a retry delivers it twice. |
event | The event name from the catalog above. |
occurred_at | When the interaction happened. |
schema_version | Envelope version, currently 1. |
workspace | { id, slug }. The numeric id is the stable key, the slug follows renames. |
source | Which surface produced the event. |
page | The page the interaction happened on. |
session_id | Anonymous session identifier, lets you group one visitor's events. |
context | { referrer_host, utm, country }, acquisition context for the session. |
properties | Event-specific details, e.g. playback progress, search query, CTA destination host. |
A realistic two-event batch:
{
"events": [
{
"event_id": "evt_01j9wxr8q4nd6t",
"event": "update_video_progress",
"occurred_at": "2026-08-04T14:07:31Z",
"schema_version": 1,
"workspace": { "id": 4821, "slug": "acme" },
"source": "update_center",
"page": "/updates/summer-release",
"session_id": "b3c9e2a4-5f7d-4c1e-9a2b-8d4f6e1c0a73",
"context": {
"referrer_host": "news.ycombinator.com",
"utm": { "utm_source": "newsletter" },
"country": "DE"
},
"properties": { "progress": 50 }
},
{
"event_id": "evt_01j9wxz2k7rtq8",
"event": "subscriber_created",
"occurred_at": "2026-08-04T14:08:02Z",
"schema_version": 1,
"workspace": { "id": 4821, "slug": "acme" },
"source": "update_center",
"page": "/updates",
"session_id": "b3c9e2a4-5f7d-4c1e-9a2b-8d4f6e1c0a73",
"context": {
"referrer_host": null,
"utm": null,
"country": "DE"
},
"properties": {
"email": "dana@example.com",
"consent": {
"granted_at": "2026-08-04T14:08:02Z",
"source": "update_center"
}
}
}
]
}Illustrative values. The exact properties each event carries are documented alongside it in the event picker.
Delivery semantics
Respond with a 2xx status promptly, anything else (or a timeout) counts as a failed delivery and enters the retry schedule.
| Behavior | Details |
|---|---|
| Latency | Near real time, events typically arrive about a minute after the interaction. |
| Batching | Up to 50 events per delivery, in one events array. |
| Dedup | Retries can deliver an event more than once. event_id is stable, dedup on it consumer-side. |
| Retries | Failed deliveries are retried automatically, 6 attempts spread over roughly 11 hours. |
| Auto-pause | An endpoint that keeps failing is paused automatically, so a dead receiver doesn't retry forever. |
| Delivery log | Every delivery is logged, and failed deliveries can be replayed from the log. |
| Test event | Fire a sample delivery on demand to verify your receiver end to end. |
| Backfill | None. Streaming starts when the endpoint connects, historical events are not replayed. |
Key your data on the workspace numeric id. The workspace slug can change when a workspace is renamed, the numeric workspace.id stays stable across renames.
Availability
Destinations is available on Grow and above (Grow, Global, and Custom), and configuration is limited to workspace admins. The dashboards themselves are part of Analytics on Launch and above.
Billing & plans