Webhooks
Subscribe to events, read the delivery format and verify the signature, plus retry behavior and delivery history.
Last updated 2026-10-23
Webhooks send an HTTP notification when a list, a search, enrichment, an import or a monitor changes. They are created and managed through the REST API, failures are retried, and every attempt can be inspected. This page covers subscribing, the request format, signature verification and retries.
Create a subscription
Send a POST /v1/webhooks:
{
"url": "https://example.com/hooks/renewsell",
"events": ["item.created", "enrichment.completed", "search.completed"]
}
urlmust be a public HTTP(S) address, not a private network or localhost, and at most 2048 characters.eventstakes at least 1 and at most 19 values.- The response's
secretis the signing secret, starts withwhsec_, and is returned only this once; store it safely. - Manage subscriptions with
GET /v1/webhooks,GET /v1/webhooks/{id},PATCH /v1/webhooks/{id}andDELETE /v1/webhooks/{id}.
Delivery format
Each delivery is a POST with a JSON body:
{
"id": "event_…",
"object": "event",
"type": "item.created",
"data": {},
"createdAt": "2026-10-23T08:30:00.000Z"
}
Headers:
| Header | Content |
|---|---|
webhook-id | Event id, for de-duplication |
webhook-timestamp | Unix timestamp in seconds |
webhook-signature | v1, plus the signature |
content-type | application/json |
Event types
| Event | When it fires |
|---|---|
search.created | A search is created |
search.updated | Search status or progress changes |
search.completed | A search completes |
search.canceled | A search is canceled |
search.failed | A search fails |
item.created | A result is added to a list |
enrichment.completed | An enrichment column finishes one cell |
import.created | An import is created |
import.completed | An import completes |
import.deleted | An import is deleted |
monitor.created | A monitor is created |
monitor.updated | A monitor is updated |
monitor.deleted | A monitor is deleted |
monitor.run.created | A monitor run starts |
monitor.run.completed | A monitor run completes |
monitor.run.canceled | A monitor run is canceled |
Target creation, update and deletion also send events, but those three type names still use an older naming that does not match the Target vocabulary in the app. Before subscribing, copy the exact name from the type returned by GET /v1/events, or from the events field of a webhook you already created.
Verify the signature
The signed string is <Webhook id>.<webhook-timestamp>.<raw body>. Sign it with HMAC-SHA256 using the secret returned at creation, encode the result as base64url, and prefix it with v1,. The webhook id is the id returned at creation (also visible in GET /v1/webhooks). The webhook-id header is the event id, for de-duplication; it is not part of the signature.
WEBHOOK_ID="webhook_…"
SECRET="whsec_…"
TIMESTAMP="1750000000"
BODY="$(cat payload.json)"
printf '%s.%s.%s' "$WEBHOOK_ID" "$TIMESTAMP" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET" -binary \
| openssl base64 -A | tr '+/' '-_' | tr -d '='
Compare the output with the part of webhook-signature after v1,. Only process the request when they match.
NOTE
Verify before processing. Drop any request whose signature does not match instead of acting on its body.
Retries
- A 2xx response is a success and is not retried.
- A 429, a 5xx or a network error is retried. The delay starts at about 2 seconds and doubles each time, capped at one hour.
- Any other 4xx is not retried; that delivery is marked failed.
- Redirects are followed up to 5 times, and every new address is re-checked as a public address.
- Events are picked up by a background sweep, so a delivery usually arrives within a few minutes.
Inspect deliveries
GET /v1/webhooks/{id}/attempts returns the time, status code, response headers and response body (bounded) of each attempt. Filter with eventType and successful. Each delivery keeps its last 10 attempts.