Skip to main content
Webhooks let BrezelScraper send an HTTP POST to your server when a job completes, fails, or is cancelled. You register a URL, and BrezelScraper calls it with a small JSON payload every time a job reaches a final state.

Quick start

  1. Go to Dashboard > Integrations and create a webhook.
  2. Paste an HTTPS endpoint URL from your server or automation tool.
  3. Copy the signing secret and store it securely. You will only see it once.
  4. Create a scraping job. When the job finishes, BrezelScraper sends a POST to your URL.
  5. Your endpoint receives the event, verifies the signature, and fetches results through the API.

Event types

BrezelScraper sends one of three event types:

Payload format

Every webhook delivery is an HTTP POST with a JSON body:
The payload contains metadata only. To get the scraped data, call GET /api/v1/jobs/{job_id}/results after receiving the webhook.

Headers

Each delivery includes these headers: The signature header contains two parts separated by a comma:
  • t=<unix_seconds> is the timestamp when the delivery was sent
  • sha256=<hex> is the HMAC-SHA256 of <timestamp>.<body> using your signing secret
The timestamp is included in the signed payload, so it cannot be forged separately.

Verify the signature

Parse the X-Webhook-Signature header, extract the timestamp and signature hash, then compute the expected HMAC over timestamp + "." + body.

Node.js

Python

Go

Always use constant-time comparison (timingSafeEqual in Node.js, compare_digest in Python, subtle.ConstantTimeCompare in Go) instead of === or ==. Regular string comparison can leak timing information that helps attackers forge signatures.
The timestamp is cryptographically bound to the signature. Reject deliveries where t is more than 5 minutes old to prevent replay attacks.

Retries

If your endpoint returns a non-2xx status code or does not respond, BrezelScraper retries the delivery up to 5 times with exponential backoff and jitter:
  • Backoff: roughly 2^attempt seconds, multiplied by a random jitter factor in [0.5, 1.5)
  • Cap: 1 hour between attempts
After 5 failed attempts the delivery is marked as failed and not retried again. Each retry uses the same X-Webhook-ID, so you can deduplicate on that header if your endpoint received the same delivery more than once.

Health states and the circuit breaker

Every webhook configuration carries a health_state: Each delivery has its own 5-retry budget with exponential backoff. The breaker counter only increments when a delivery has exhausted that budget — so disabled means “10 separate jobs failed end-to-end,” not “10 individual HTTP attempts.” A single successful (2xx) delivery anywhere in the streak resets consecutive_failures to 0 and restores health_state to healthy.

Re-enabling a disabled webhook

To re-enable after fixing your endpoint, PATCH the config with {"reenable": true}:
You can mix reenable with name / url in the same request — the re-enable is applied first, so a re-enable failure won’t half-apply your metadata change.

Listing disabled webhooks

GET /api/v1/webhooks returns every config you own, including disabled ones, so the UI can render a “Re-enable” CTA. Each entry now includes:
disabled_reason is one of:
  • http_5xx — your endpoint returned a 5xx
  • http_4xx — your endpoint returned a 4xx
  • transport_error — connection refused, TLS error, timeout, etc.
It captures the failure mode that was active when the breaker tripped.

Rate limits

To protect your endpoint and prevent abuse, BrezelScraper limits webhook deliveries to:
  • 100 deliveries per hour per user account
  • 50 deliveries per hour per destination IP address
If a delivery is rate-limited, it is automatically retried after the limit window resets.

Requirements and limits

URLs pointing to private networks, localhost, or cloud metadata endpoints are rejected.

Manage webhook configurations

POST /api/v1/webhooks

Create a new webhook. Request body:
Response (201 Created):
The secret is your signing key. Store it securely. It is shown only at creation time.

GET /api/v1/webhooks

List all webhook configurations for your account, including revoked ones. Response (200 OK):
verified_at is set after the first successful delivery to this webhook. This endpoint returns a plain array because each account is limited to 10 webhooks. No pagination is needed.

PATCH /api/v1/webhooks/{id}

Update a webhook’s name, URL, or re-enable the circuit breaker. All fields are optional.
reenable: true clears any disabled state and resets consecutive_failures to 0. Sending reenable: true on an already-healthy config is a no-op success (the call is idempotent). reenable: false is also a no-op — only the delivery worker can disable a config; users only ever re-enable. Returns 204 No Content on success.

DELETE /api/v1/webhooks/{id}

Revoke a webhook. Revoked webhooks stop receiving deliveries. Returns 204 No Content on success.

Best practices

  • Respond quickly. Return a 200 status code as fast as possible. If you need to do heavy processing, queue the work and respond immediately.
  • Verify every delivery. Always check the X-Webhook-Signature header before acting on the payload.
  • Deduplicate. Use X-Webhook-ID to detect and skip duplicate deliveries.
  • Fetch results from the API. The webhook payload tells you a job finished. Call GET /api/v1/jobs/{job_id}/results to get the actual data.
  • Handle all event types. Your endpoint should return 200 for events it does not care about. Returning an error causes unnecessary retries.