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 |
|---|---|
target.created | A Target is created |
target.updated | A Target is updated |
target.deleted | A Target is deleted |
search.created | A search is created |
search.updated | A draft search is edited |
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 |
Ids in event data, such as Target ids, are opaque strings. Do not rely on their prefix.
Verify the signature
Deliveries follow the Standard Webhooks scheme, so any Standard Webhooks library can verify them. To verify by hand:
- Build the signed string:
<webhook-id header>.<webhook-timestamp header>.<raw body>. - Take the
secretreturned at creation, drop thewhsec_prefix and base64-decode the rest. This is the HMAC key. - Compute HMAC-SHA256 of the signed string and encode it as standard base64. Prefix it with
v1,. - Compare it with each space-separated value in
webhook-signature, and process the request only when one matches.
SECRET="whsec_…"
WEBHOOK_ID="<webhook-id header>"
TIMESTAMP="<webhook-timestamp header>"
BODY="$(cat payload.json)"
KEY="$(printf '%s' "${SECRET#whsec_}" | base64 -d | xxd -p -c 256)"
printf '%s.%s.%s' "$WEBHOOK_ID" "$TIMESTAMP" "$BODY" \
| openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY" -binary \
| openssl base64 -A
With a library, pass the raw body and the three headers:
import { Webhook } from "standardwebhooks";
const wh = new Webhook(process.env.RENEWSELL_WEBHOOK_SECRET); // "whsec_…"
const event = wh.verify(rawBody, {
"webhook-id": headers["webhook-id"],
"webhook-timestamp": headers["webhook-timestamp"],
"webhook-signature": headers["webhook-signature"],
});
The webhook-id header is the event id, and it is the same value used in the signed string. Also reject requests whose webhook-timestamp is more than a few minutes old.
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.