Webhook payload reference
Configure Apex webhooks, verify signatures, and handle the event payloads Apex delivers today.
Apex webhooks notify your system after selected experiment, Guardian, screenshot, and publication events. Webhooks are shop-scoped. Each endpoint has a URL, subscribed event list, active flag, and generated signing secret.
Create an endpoint
Create webhooks with the public API:
POST /api/v1/webhooks
Authorization: Bearer apx_your_key_here
Content-Type: application/json
{
"url": "https://example.com/apex/webhooks",
"events": ["experiment.started", "experiment.completed"]
}The URL must be HTTPS. The create response includes the generated secret once. List, read, and update responses do not include it.
Event names
These are the event names accepted by the public webhook create/update schema:
| Event | When Apex enqueues it |
|---|---|
experiment.started | An experiment starts from the dashboard launch flow or scheduled launch worker. |
experiment.completed | A public API request completes an experiment. |
experiment.significant | Auto-stop pauses an experiment because a significant winner was found. |
experiment.guardian_triggered | Guardian detects a warning and alerts without pausing. |
experiment.guardian_paused | Guardian pauses an experiment for a configured safety finding. |
variation.winning | Auto-stop identifies the winning variation for the primary goal. |
screenshots.completed | A screenshot job finishes and stores screenshot results. |
project.published | Runtime publication records a shop revision when publication webhooks are enabled. |
Delivery envelope
Every outbound delivery body is JSON. Apex serializes the stored event payload, then adds delivery metadata:
{
"event": "experiment.started",
"experiment_id": "11111111-1111-4111-8111-111111111111",
"experiment_name": "PDP trust badge",
"delivery_id": "22222222-2222-4222-8222-222222222222",
"timestamp": "2026-07-08T12:00:00.000Z"
}Headers:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Drip-Signature | sha256= followed by the hex HMAC SHA-256 of the exact request body using the webhook secret. |
X-Drip-Event | Event name, for example experiment.started. |
X-Apex-Delivery-Id | Delivery row ID. Use this for idempotency. |
User-Agent | Drip-Webhooks/1.0 |
Verify signatures
Verify the raw body bytes before parsing or transforming the payload:
import crypto from "node:crypto";
export function verifyApexWebhook(rawBody: string, signatureHeader: string, secret: string) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const received = Buffer.from(signatureHeader);
const expectedBytes = Buffer.from(expected);
return received.length === expectedBytes.length
&& crypto.timingSafeEqual(received, expectedBytes);
}Payload shapes
Common metadata is always added by the dispatcher:
| Field | Type | Notes |
|---|---|---|
event | string | Event name. |
delivery_id | string | Unique Apex delivery ID. |
timestamp | ISO datetime string | Delivery serialization time. |
Event-specific fields:
| Event | Fields |
|---|---|
experiment.started | experiment_id, experiment_name |
experiment.completed | experiment_id, experiment_name |
experiment.significant | experiment_id, experiment_name, goal_id, winning_variation_id |
variation.winning | experiment_id, experiment_name, variation_id, goal_id |
experiment.guardian_triggered | experiment_id, experiment_name, kind, action, variation_id, goal_id, details |
experiment.guardian_paused | experiment_id, experiment_name, reason, variation_id, goal_id |
screenshots.completed | job_id, experiment_id, results |
project.published | project_id, revision, environment_key, status, published_at |
goal_id can be null where the triggering condition does not apply to a specific goal. details and results are JSON objects whose internal shape depends on the Guardian finding or screenshot job output.
Retries and failure behavior
Apex queues matching active webhooks and processes pending deliveries asynchronously. A delivery is claimed as processing, sent with a 10 second timeout, and marked delivered only when the receiver returns a 2xx status.
Failed fetches and non-2xx responses are retried up to five attempts with exponential backoff. Retry delay starts at 15 seconds and is capped at 30 minutes. Processing deliveries older than 10 minutes are treated as stale and can be claimed again.
After the final failed attempt, the delivery is marked dead. The parent webhook's consecutive failure counter is incremented. After three consecutive dead deliveries, Apex disables the webhook and sets disabled_at.
Inspect deliveries
Use the delivery log endpoint to debug recent attempts:
GET /api/v1/webhooks/22222222-2222-4222-8222-222222222222/deliveries?status=dead&limit=20
Authorization: Bearer apx_your_key_hereValid status filters are queued, processing, delivered, failed, and dead. limit defaults to 50 and is clamped from 1 to 100.