Choose your next task
First build and verify a receiver, then register its public HTTPS URL, create a watch and confirm a delivered test. Registration, queuing and delivery are different outcomes. Use the recovery and rotation procedures below only when operating an existing subscription.
Register your endpoint
Use a live API key with webhooks:write and the webhooks:manage subscription entitlement. Only public HTTPS endpoints on port 443 are accepted. Store the returned secret securely: listing endpoints never reveals it. Test keys manage separate subscriptions but never send network deliveries.
curl --fail-with-body https://hintadata.fi/v1/webhooks \
-H "Authorization: Bearer $HINTADATA_API_KEY" \
-H 'Content-Type: application/json' -H 'Idempotency-Key: endpoint-create-001' \
-d '{"url":"https://receiver.example/events","events":["watchlist.triggered"]}'Create a webhook watch
Create a watch with channel=webhook and no email destination. A new source snapshot that moves an active watch below its target queues watchlist.triggered for all enabled matching subscriptions in the workspace. Registration does not backfill old events. Email watches remain a separate channel through the operator email bridge.
{"canonical_product_id":"sony_wh1000xm5_black","condition":"target_price","target_price":250,"channel":"webhook"}POST this body to /v1/watchlists with watchlists:write and a new Idempotency-Key. Live source evidence must already exist. delivery_unavailable means the operator has not configured this delivery channel.
Manage a subscription
The dashboard uses the same subscriptions as the API. Edit the HTTPS destination and event filters, pause/resume, rotate the signing secret, and inspect paginated history, metrics and audit. GET /v1/webhook-metrics summarizes retained deliveries; GET /v1/webhook-audit returns the latest 100 management entries (up to 1,000 retained for 30 days). Actors are one-way references, never credentials. last_error_code distinguishes HTTP, DNS, TLS, timeout, transport, encryption and source-validity failures without exposing response bodies.
Send a test and verify delivery
POST /v1/webhooks/{id}/test with {"confirm":true} and an Idempotency-Key explicitly queues a live webhook.test event containing only {"test":true}. It has no customer data and is limited to one test per enabled endpoint per minute. The worker sends later; a queued response is not proof of delivery. Test-mode API keys never send. Verify and deduplicate synthetic tests like other events, but do not apply business effects.
Read GET /v1/webhook-deliveries?subscription_id={id}&limit=25, follow next_offset if needed and find the returned delivery ID. Success requires delivered status plus the receiver receipt; pending or sending is not complete. If failed, follow the delivery diagnosis below before requesting replay.
Replay a failed delivery safely
POST /v1/webhook-deliveries/{id}/retry with a fresh Idempotency-Key requests an audited replay of a failed delivery. Source evidence must still be current, the endpoint enabled and its URL/filters unchanged. The original event and delivery IDs stay unchanged. At most three manual replays within seven days, eight attempts per cycle (32 lifetime attempts). Cancelled or expired observations cannot be revived. Retry an uncertain request with the same key; never remove receiver deduplication.
Verify before processing
Verify X-Webhook-Signature against the exact raw body: t=<seconds>,v1=<hex HMAC-SHA256(secret, timestamp + "." + body)>. Reject timestamps outside five minutes. Then transactionally deduplicate event.id / Idempotency-Key with your business effect. Delivery is at least once and ordering is not guaranteed. Never use parsed and re-serialized JSON for signature verification.
import { verifyWebhook } from '@pricecompare/sdk';
const event = await verifyWebhook(rawBody, signatureHeader, signingSecret);
// Persist event.id and your business effect atomically before returning 2xx.from price_compare.webhooks import verify_webhook
event = verify_webhook(raw_body_bytes, signature_header, signing_secret)
# Persist event["id"] and your business effect atomically before returning 2xx.Diagnose a missing delivery
| What you see | What to check next |
|---|---|
| No delivery row | Check workspace, environment, subscription event filter and the triggering source update. Registration does not backfill events. |
| pending / sending | Inspect next retry and worker status. Do not request another event just because delivery is not immediate. |
| failed | Read last_error_code and attempt_history. Repair DNS/TLS, the receiver or signing configuration before requesting replay. |
| delivered | A 2xx was received. Check the receiver transaction and event ID, not only the HTTP log. |
| cancelled | Check subscription changes and source expiry. Cancelled observations cannot be revived. |
GET /v1/webhook-deliveries?limit=25 returns pending, sending, delivered, failed or cancelled deliveries. Filter by subscription_id and continue with next_offset. attempt_history records each start, finish, duration, safe error code, HTTP status and next retry without bodies or secrets. Any 2xx acknowledges. HTTP 4xx is permanent except 408, 409, 425 and 429; destination denial is also permanent. Other failures retry up to eight attempts per cycle with jittered exponential backoff and bounded Retry-After (one hour). Three consecutive transient failures pause the endpoint for five minutes; a permanent failure pauses it for 15 minutes. After cooldown only one probe is allowed. Worker scheduling can delay delivery further. History is retained seven days.
Acknowledge and resolve an incident
Receiver incidents are separate from delivery history. POST /v1/webhooks/{id}/incidents/acknowledge with {"incident_id":"wi_..."} and a new Idempotency-Key stops reminders without hiding unresolved failures or cancelling retries. Repair the receiver and send a test or replay a current failed delivery. A successful delivery resolves the incident and triggers recovery notification; old failures remain visible. Acknowledgement alone is not recovery.
Pause or remove a subscription
PATCH /v1/webhooks/{id} with {"enabled":false} pauses; DELETE removes. Destination/filter changes cancel queued deliveries. Changing the URL or explicitly resuming resets the cooldown. A source revision change or expiry cancels unsent observations; in-flight requests cannot be recalled.
Prepare, activate and retire a secret
POST /v1/webhooks/{id}/secret-rotation with a new Idempotency-Key prepares a secret for 24 hours without changing signing. Install it at your receiver alongside the current secret. Then POST /v1/webhooks/{id}/secret-rotation/activate with {"rotation_id":"wr_...","confirm":true} and a different Idempotency-Key. Signing switches atomically. Keep accepting the old secret for at least 60 seconds for in-flight requests, then retire it at the receiver. A preparation can be discarded with POST /v1/webhooks/{id}/secret-rotation/cancel and {"rotation_id":"wr_..."}. Creation and preparation replay their original secret response for 24 hours; list, activation and cancellation never reveal secrets. API contract 2.0.0 removes the old immediate-rotation operation.