Webhooks
Outbound webhooks send an HTTP POST to your endpoint when something happens in your workspace. Each delivery is signed with a secret unique to the webhook, so you can prove it came from Jelliu.
Managing webhooks
Create and manage webhooks in the dashboard under Settings → Webhooks (https://app.jelliu.co/settings?tab=webhooks), or through the API:
Create a webhook
The response (201) includes the signing secret in data.secret, a value starting with whsec_. This is the only time the secret is returned; afterwards the API shows [configured]. Store it with your other credentials.
Where deliveries are sent. Up to 2000 characters. It must resolve to a public address: private, internal and loopback targets are rejected. Use HTTPS.
At least one event name from the catalog, or "*" for every event except audit.log_recorded.
Up to 500 characters.
Only deliver events that match. See Filters.
Extra HTTP headers to send with every delivery, as name/value strings. Use valid HTTP header names and no line breaks in values: otherwise deliveries are rejected and logged as failures. Custom headers cannot replace X-Webhook-*, Host or Content-Length.
Custom JSON body. See Payload templates. Up to 10000 characters.
PATCH accepts the same fields, all optional, plus is_active to disable or re-enable the webhook.
Event catalog
A subscription can name any event below. The Sent column shows which events Jelliu currently emits; the others are accepted in subscriptions but are not delivered yet.
Payload
Unless you set a payload template, the body is:
data depends on the event:
Treat every field as optional and ignore fields you do not recognize.
Delivery headers
Verify X-Webhook-Signature-V2. The legacy signatures do not cover the timestamp, so a captured delivery could be replayed against you indefinitely.
X-Webhook-Timestamp is an ISO 8601 string, not a Unix number. It is set once when the event is emitted and stays the same on every retry.
Verifying signatures
- Read the raw request body, before any JSON parsing.
- Build the signed string: the
X-Webhook-Timestampvalue, a period, then the raw body. - Compute HMAC-SHA256 of that string with your full secret (including the
whsec_prefix) as the key, and hex-encode it. - Compare it with
X-Webhook-Signature-V2using a constant-time comparison. - Reject timestamps outside your tolerance window. Because retries reuse the original timestamp, allow enough time for them; the examples use 10 minutes.
There is no delivery ID header. A retry carries the same timestamp and body, so its X-Webhook-Signature-V2 is identical to the first attempt’s (unless the secret was rotated in between). Use the signature as an idempotency key to drop duplicates.
Delivery and retries
Before each attempt Jelliu reloads the webhook, so a rotated secret applies to pending retries and a disabled or deleted webhook stops receiving them.
Automatic disabling. Failures are counted per webhook, and any successful delivery resets the count. When a webhook reaches 10 consecutive failures it is disabled and the workspace gets an in-app notification. Fix the endpoint, then re-enable it with PATCH /api/webhooks/{webhookId} and { "is_active": true }, which also resets the count.
Respond with 2xx as soon as the signature checks out and do the work asynchronously, so slow processing does not turn into timeouts.
Delivery logs
GET /api/webhooks/{webhookId}/delivery-logs?limit=20 returns the most recent attempts. Jelliu keeps the last 25 per webhook; limit accepts 1 to 50 and defaults to 20.
status is 0 when no HTTP response was received (timeout, connection error, or a delivery rejected before sending). delivered_at is the event timestamp. Response bodies from your endpoint are truncated to 500 characters in error.
Rotating the secret
Rotate a secret from the dashboard, which calls POST /api/webhooks/{webhookId}/rotate-secret. The new secret is returned once, in data.secret, and takes effect immediately, including for deliveries still waiting to be retried. There is no overlap period with the old secret, so deploy the new value to your receiver right after rotating.
API keys cannot rotate secrets, even with the full scope. That prevents a leaked key from silently taking over your integration.
Filters
Filters narrow which events reach the endpoint. All filters you set must match.
Filters fail closed. If you filter on agentIds and an event does not carry agentId, it is not delivered. For the same reason, a webhook subscribed to audit.log_recorded cannot use campaignIds, agentIds, outcomes or minSentiment: the API rejects that combination. Use auditActions, or a separate webhook.
Payload templates
payload_template replaces the default body with your own JSON. Placeholders:
Placeholders inside quotes are inserted as escaped text; outside quotes they are inserted as JSON values. The template must render to valid JSON. Jelliu checks it when you save, and if it cannot be rendered at delivery time the event is not sent and the reason appears in the delivery logs. The signature always covers the body exactly as sent.

