```yaml product: AlterLab title: Webhooks category: guides comparison_context: "AlterLab is an alternative to Firecrawl, ScrapingBee, and Bright Data." source_url: https://alterlab.io/docs/guides/webhooks ``` # Webhooks Receive real-time notifications when your scraping jobs complete, fail, or require attention. No more polling -- let AlterLab push results directly to your server. ## How Webhooks Work 1. **Job Completes** -- When a scraping job finishes, AlterLab checks if you have webhooks configured 2. **Payload Signed** -- The event payload is signed with your webhook secret using HMAC-SHA256 3. **HTTP POST Sent** -- AlterLab sends a POST request to your webhook URL (10 second timeout) 4. **Response Handled** -- A 2xx response indicates success. Other status codes trigger retries ## Setup Guide ### 1. Create a Webhook Endpoint Your endpoint must accept POST requests and return 200: ```python from flask import Flask, request import hmac, hashlib app = Flask(__name__) WEBHOOK_SECRET = "whsec_..." @app.route("/webhooks/alterlab", methods=["POST"]) def handle_webhook(): signature = request.headers.get("X-Alterlab-Signature", "") expected = "sha256=" + hmac.new( WEBHOOK_SECRET.encode(), request.data, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(signature, expected): return "Invalid signature", 401 event = request.json if event["event"] == "scrape.completed": process_result(event["data"]) return "OK", 200 ``` ### 2. Register Your Webhook ```bash curl -X POST https://api.alterlab.io/api/v1/user-webhooks \ -H "Authorization: Bearer YOUR_SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhooks/alterlab", "events": ["scrape.completed", "scrape.failed", "batch.completed"] }' ``` ## Event Types | Event | Description | | ------------------ | -------------------------------------------------- | | `scrape.completed` | A single scrape job succeeded | | `scrape.failed` | A single scrape job failed after all retries | | `batch.completed` | All jobs in a batch finished (succeeded or failed) | ## Payload Format ```json { "event": "scrape.completed", "timestamp": "2026-03-03T12:00:00Z", "data": { "job_id": "a1b2c3d4-...", "url": "https://example.com", "status": "succeeded", "result": { "content": "...", "title": "Example Domain", "status_code": 200 }, "billing": { "credits_used": 1, "tier_used": "curl" } } } ``` ## Signature Verification Every webhook delivery includes: - `X-Alterlab-Signature`: HMAC-SHA256 signature (`sha256=...`) - `X-Alterlab-Delivery-Id`: Unique delivery ID - `X-Alterlab-Event`: Event type - `X-Alterlab-Timestamp`: Unix timestamp of the delivery Always verify the signature before processing events. ### Lifecycle Webhooks (scrape.completed, scrape.failed, batch.completed) Lifecycle webhooks sign the **raw request body** only: ``` HMAC-SHA256(secret, raw_body) ``` The verification code in the setup example above uses this format. ### Stream Connector Webhooks (crawl.page_completed) Stream connector webhooks — delivered via batch `output_config.webhook_stream` — use a **timestamp-prefixed** signed message for replay protection: ``` HMAC-SHA256(secret, "{X-Alterlab-Timestamp}.{raw_body}") ``` To verify a stream connector signature: ```python import hmac, hashlib def verify_stream_signature(request_headers, raw_body: bytes, secret: str) -> bool: timestamp = request_headers.get("X-Alterlab-Timestamp", "") signature = request_headers.get("X-Alterlab-Signature", "") signed_message = f"{timestamp}.{raw_body.decode('utf-8')}".encode("utf-8") expected = "sha256=" + hmac.new( secret.encode(), signed_message, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) ``` > **Why different formats?** Stream connector events are bound to a specific timestamp in the signature, so replaying an old request with a valid signature will fail if the `X-Alterlab-Timestamp` value is changed. Lifecycle webhooks rely on the delivery ID for deduplication instead. ## Retry Behavior - Up to **3 retry attempts** on failure - **Exponential backoff**: 10s, 60s, 300s - A delivery is considered failed if your server returns non-2xx or times out (10s) ## Best Practices 1. **Always verify signatures** -- Prevents spoofed webhook deliveries 2. **Return 200 quickly** -- Process events asynchronously; acknowledge receipt immediately 3. **Handle duplicates** -- Use the delivery ID to deduplicate in case of retries 4. **Use HTTPS** -- Always use HTTPS endpoints for webhook URLs 5. **Monitor deliveries** -- Check the delivery log for failed deliveries