> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reducto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Svix webhooks notifications

> This guide demonstrates how to configure Svix for webhook notifications on job updates with built-in security, retries, and delivery tracking.

Webhooks allow your application to receive real-time updates when long-running or asynchronous jobs are completed — such as parsing or extraction tasks submitted via Reducto's API.

Instead of continuously polling the job status, you can configure a webhook endpoint to be notified automatically when a job finishes. Svix provides enterprise-grade webhook infrastructure with built-in security (request signing), automatic retries with exponential backoff, delivery tracking, and a management dashboard.

## Why Use Svix Webhooks?

Svix webhooks offer several advantages over direct webhooks:

* **Built-in Security**: Automatic request signing with cryptographic verification
* **Reliable Delivery**: Advanced retry logic with exponential backoff
* **Delivery Dashboard**: Web UI to monitor webhook deliveries and debug failures
* **Multiple Endpoints**: Support for multiple webhook endpoints via channels
* **Event Filtering**: Route different event types to different endpoints
* **Replay Capability**: Manually replay failed webhook deliveries

## How Svix Webhooks Work

When you submit an async job with Svix webhooks configured, Reducto will:

1. Process your document asynchronously
2. Send a signed webhook notification to Svix when the job completes (or fails)
3. Svix delivers the notification to your configured endpoint(s) with automatic retries
4. Your application verifies the signature and processes the webhook payload

## Setting Up Svix Webhooks

### Step 1: Configure Your Webhook Endpoint

First, get the Svix configuration URL to set up your webhook endpoints:

<CodeGroup>
  ```python Python theme={null}
  import requests

  webhook_url = requests.post(
      "https://platform.reducto.ai/configure_webhook",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
  )

  # Go to this link to configure your webhook receiving application
  print(webhook_url.text)
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://platform.reducto.ai/configure_webhook", {
    method: "POST",
    headers: {
      "Authorization": "Bearer YOUR_API_KEY"
    }
  });

  const webhookUrl = await response.text();

  // Go to this link to configure your webhook receiving application
  console.log(webhookUrl);
  ```
</CodeGroup>

This will return a URL to the Svix dashboard where you can:

* Add webhook endpoint URLs
* Configure channels for routing
* View delivery logs and debug failures
* Get your webhook signing secret

### Step 2: Submit Jobs with Svix Webhooks

<CodeGroup>
  ```python Python theme={null}
  from reducto import Reducto

  client = Reducto(api_key="your_api_key")

  # Submit an async parse job with Svix webhooks
  submission = client.parse.run_job(
      input="https://ci.reducto.ai/onepager.pdf",
      async_config={
          "webhook": {
              "mode": "svix",
              "channels": []  # Optional: specify channels or omit to send to all
          },
          "metadata": {
              "user_id": "user_123",
              "document_type": "invoice"
          }
      }
  )

  print(f"Job submitted: {submission.job_id}")
  ```

  ```typescript TypeScript theme={null}
  import { Reducto } from "@reducto/sdk";

  const client = new Reducto({
    apiKey: "your_api_key"
  });

  // Submit an async parse job with Svix webhooks
  const submission = await client.parse.runJob({
    input: "https://ci.reducto.ai/onepager.pdf",
    async: {
      webhook: {
        mode: "svix",
        channels: []  // Optional: specify channels or omit to send to all
      },
      metadata: {
        userId: "user_123",
        documentType: "invoice"
      }
    }
  });

  console.log(`Job submitted: ${submission.jobId}`);
  ```
</CodeGroup>

### Using Channels for Routing

Channels allow you to route webhooks to different endpoints. For example, you might want to send production jobs to one endpoint and development jobs to another:

<CodeGroup>
  ```python Python theme={null}
  from reducto import Reducto

  client = Reducto(api_key="your_api_key")

  # Send to production channel
  production_submission = client.parse.run_job(
      input="https://ci.reducto.ai/onepager.pdf",
      async_config={
          "webhook": {
              "mode": "svix",
              "channels": ["production"]
          }
      }
  )

  # Send to development channel
  dev_submission = client.parse.run_job(
      input="https://ci.reducto.ai/test.pdf",
      async_config={
          "webhook": {
              "mode": "svix",
              "channels": ["development"]
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  import { Reducto } from "@reducto/sdk";

  const client = new Reducto({ apiKey: "your_api_key" });

  // Send to production channel
  const productionSubmission = await client.parse.runJob({
    input: "https://ci.reducto.ai/onepager.pdf",
    async: {
      webhook: {
        mode: "svix",
        channels: ["production"]
      }
    }
  });

  // Send to development channel
  const devSubmission = await client.parse.runJob({
    input: "https://ci.reducto.ai/test.pdf",
    async: {
      webhook: {
        mode: "svix",
        channels: ["development"]
      }
    }
  });
  ```
</CodeGroup>

### Webhook Handler with Signature Verification

<Accordion title="View webhook handler code (Python / TypeScript)">
  <CodeGroup>
    ```python Python theme={null}
    from flask import Flask, request, jsonify
    from reducto import Reducto
    from svix.webhooks import Webhook, WebhookVerificationError

    app = Flask(__name__)
    client = Reducto(api_key="your_api_key")

    # Get this from the Svix dashboard
    SVIX_WEBHOOK_SECRET = "whsec_your_webhook_secret"

    @app.route('/webhook/reducto', methods=['POST'])
    def handle_webhook():
        # Get the webhook signature headers
        svix_id = request.headers.get('svix-id')
        svix_timestamp = request.headers.get('svix-timestamp')
        svix_signature = request.headers.get('svix-signature')
        
        if not all([svix_id, svix_timestamp, svix_signature]):
            return jsonify({"error": "Missing signature headers"}), 401
        
        # Verify the webhook signature
        wh = Webhook(SVIX_WEBHOOK_SECRET)
        try:
            payload = wh.verify(request.data, {
                'svix-id': svix_id,
                'svix-timestamp': svix_timestamp,
                'svix-signature': svix_signature
            })
        except WebhookVerificationError as e:
            return jsonify({"error": "Invalid signature"}), 401
        
        # Process the verified webhook
        job_id = payload['job_id']
        status = payload['status']
        metadata = payload.get('metadata', {})
        
        if status == "Completed":
            # Retrieve the job result
            job = client.job.get(job_id)
            result = job.result
            
            # Process the result
            print(f"Job {job_id} completed successfully")
            print(f"Metadata: {metadata}")
            print(f"Result: {result}")
            
        elif status == "Failed":
            print(f"Job {job_id} failed")
        
        return jsonify({"received": True}), 200

    if __name__ == '__main__':
        app.run(port=5000)
    ```

    ```typescript TypeScript theme={null}
    import express from 'express';
    import { Reducto } from "@reducto/sdk";
    import { Webhook } from "svix";

    const app = express();
    const client = new Reducto({ apiKey: "your_api_key" });

    // Get this from the Svix dashboard
    const SVIX_WEBHOOK_SECRET = "whsec_your_webhook_secret";

    // Important: Use express.raw() for webhook endpoints to preserve the raw body
    app.post('/webhook/reducto', express.raw({ type: 'application/json' }), async (req, res) => {
      // Get the webhook signature headers
      const svixId = req.headers['svix-id'] as string;
      const svixTimestamp = req.headers['svix-timestamp'] as string;
      const svixSignature = req.headers['svix-signature'] as string;
      
      if (!svixId || !svixTimestamp || !svixSignature) {
        return res.status(401).json({ error: "Missing signature headers" });
      }
      
      // Verify the webhook signature
      const wh = new Webhook(SVIX_WEBHOOK_SECRET);
      let payload: any;
      
      try {
        payload = wh.verify(req.body, {
          'svix-id': svixId,
          'svix-timestamp': svixTimestamp,
          'svix-signature': svixSignature
        });
      } catch (err) {
        return res.status(401).json({ error: "Invalid signature" });
      }
      
      // Process the verified webhook
      const { job_id, status, metadata } = payload;
      
      if (status === "Completed") {
        // Retrieve the job result
        const job = await client.job.retrieve(job_id);
        const result = job.result;
        
        // Process the result
        console.log(`Job ${job_id} completed successfully`);
        console.log(`Metadata:`, metadata);
        console.log(`Result:`, result);
        
      } else if (status === "Failed") {
        console.log(`Job ${job_id} failed`);
      }
      
      res.status(200).json({ received: true });
    });

    app.listen(5000, () => {
      console.log('Webhook server listening on port 5000');
    });
    ```
  </CodeGroup>
</Accordion>

## Using Svix Webhooks with Extract

Svix webhooks work with all async endpoints. Here's an example with the extract endpoint:

<CodeGroup>
  ```python Python theme={null}
  from reducto import Reducto

  client = Reducto(api_key="your_api_key")

  submission = client.extract.run_job(
      input="https://ci.reducto.ai/invoice.pdf",
      instructions={
          "schema": {
              "invoice_number": "string",
              "total_amount": "number",
              "date": "string"
          }
      },
      async_config={
          "webhook": {
              "mode": "svix",
              "channels": ["invoices"]
          }
      }
  )

  print(f"Extraction job submitted: {submission.job_id}")
  ```

  ```typescript TypeScript theme={null}
  import { Reducto } from "@reducto/sdk";

  const client = new Reducto({ apiKey: "your_api_key" });

  const submission = await client.extract.runJob({
    input: "https://ci.reducto.ai/invoice.pdf",
    instructions: {
      schema: {
        invoice_number: "string",
        total_amount: "number",
        date: "string"
      }
    },
    async: {
      webhook: {
        mode: "svix",
        channels: ["invoices"]
      }
    }
  });

  console.log(`Extraction job submitted: ${submission.jobId}`);
  ```
</CodeGroup>

## Webhook Payload Structure

The webhook notification will be sent as a JSON POST request with the following structure:

```json theme={null}
{
  "status": "Completed",  // or "Failed"
  "job_id": "job_abc123",
  "metadata": {
    // Your custom metadata (optional)
  }
}
```

## Verifying Webhook Signatures

Svix automatically signs all webhook requests. **Always verify the signature** to ensure the webhook is legitimate and hasn't been tampered with.

### Why Signature Verification Matters

Without signature verification, anyone could send fake webhook requests to your endpoint. Signature verification ensures:

1. The webhook came from Reducto/Svix (authenticity)
2. The payload hasn't been modified (integrity)
3. The webhook isn't a replay of an old request (freshness)

### Getting Your Webhook Secret

1. Go to the Svix dashboard (use the URL from `/configure_webhook`)
2. Navigate to your endpoint settings
3. Copy the webhook signing secret (starts with `whsec_`)
4. Store it securely as an environment variable

### Signature Verification Best Practices

1. **Always verify signatures**: Never skip signature verification in production
2. **Use the official Svix SDK**: The SDK handles signature verification correctly
3. **Preserve raw request body**: Don't parse the JSON before verification
4. **Check timestamp**: Reject webhooks with timestamps too far in the past
5. **Store secrets securely**: Use environment variables or secret management systems

## Managing Webhooks in the Svix Dashboard

The Svix dashboard provides powerful tools for managing your webhooks:

### Viewing Delivery Logs

* See all webhook deliveries with timestamps and status codes
* View request and response bodies for debugging
* Filter by endpoint, status, or time range

### Debugging Failed Deliveries

* Identify why webhooks failed (network errors, timeouts, invalid responses)
* View detailed error messages and stack traces
* Check signature verification failures

### Manual Replay

* Replay failed webhooks after fixing issues
* Replay webhooks to test new endpoint implementations
* Bulk replay multiple webhooks at once

### Endpoint Management

* Add, edit, or remove webhook endpoints
* Configure rate limits and retry policies
* Set up endpoint-specific channels and filters

## Advanced Features

### Multiple Endpoints

Configure multiple webhook endpoints to receive the same events:

<CodeGroup>
  ```python Python theme={null}
  # In the Svix dashboard, add multiple endpoints:
  # - https://api.example.com/webhooks/reducto
  # - https://backup.example.com/webhooks/reducto

  # Both endpoints will receive webhook notifications
  submission = client.parse.run_job(
      input="https://ci.reducto.ai/onepager.pdf",
      async_config={
          "webhook": {
              "mode": "svix"
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  // In the Svix dashboard, add multiple endpoints:
  // - https://api.example.com/webhooks/reducto
  // - https://backup.example.com/webhooks/reducto

  // Both endpoints will receive webhook notifications
  const submission = await client.parse.runJob({
    input: "https://ci.reducto.ai/onepager.pdf",
    async: {
      webhook: {
        mode: "svix"
      }
    }
  });
  ```
</CodeGroup>

### Channel-Based Routing

Use channels to route different types of jobs to different endpoints:

<CodeGroup>
  ```python Python theme={null}
  # Configure in Svix dashboard:
  # - Endpoint A: listens to "production" channel
  # - Endpoint B: listens to "development" channel
  # - Endpoint C: listens to all channels

  # This goes to Endpoint A and C
  prod_job = client.parse.run_job(
      input="https://ci.reducto.ai/prod-doc.pdf",
      async_config={
          "webhook": {
              "mode": "svix",
              "channels": ["production"]
          }
      }
  )

  # This goes to Endpoint B and C
  dev_job = client.parse.run_job(
      input="https://ci.reducto.ai/test-doc.pdf",
      async_config={
          "webhook": {
              "mode": "svix",
              "channels": ["development"]
          }
      }
  )
  ```

  ```typescript TypeScript theme={null}
  // Configure in Svix dashboard:
  // - Endpoint A: listens to "production" channel
  // - Endpoint B: listens to "development" channel
  // - Endpoint C: listens to all channels

  // This goes to Endpoint A and C
  const prodJob = await client.parse.runJob({
    input: "https://ci.reducto.ai/prod-doc.pdf",
    async: {
      webhook: {
        mode: "svix",
        channels: ["production"]
      }
    }
  });

  // This goes to Endpoint B and C
  const devJob = await client.parse.runJob({
    input: "https://ci.reducto.ai/test-doc.pdf",
    async: {
      webhook: {
        mode: "svix",
        channels: ["development"]
      }
    }
  });
  ```
</CodeGroup>

## Best Practices

1. **Always Verify Signatures**: Use the Svix SDK to verify webhook signatures. Never skip this step in production.

2. **Use HTTPS**: Always use HTTPS endpoints to ensure secure transmission of webhook data.

3. **Implement Idempotency**: Your webhook handler should be idempotent since Svix may retry deliveries. Use the `svix-id` header as an idempotency key.

4. **Return Quickly**: Return a 2xx status code quickly (within 15 seconds). Process the job result asynchronously if needed.

5. **Use Channels for Environments**: Separate production and development webhooks using channels to avoid mixing test and production data.

6. **Monitor the Dashboard**: Regularly check the Svix dashboard for failed deliveries and debug issues promptly.

7. **Handle Failures Gracefully**: Implement proper error handling and logging in your webhook endpoint.

8. **Use Metadata**: Include custom metadata in your webhook configuration to help identify and route jobs in your application.

9. **Test with Replay**: Use the Svix dashboard's replay feature to test your webhook handler without submitting new jobs.

10. **Set Up Alerts**: Configure alerts in your monitoring system for webhook delivery failures.

## Troubleshooting

### Webhooks Not Being Received

1. Check the Svix dashboard for delivery attempts and errors
2. Verify your endpoint URL is correct and publicly accessible
3. Ensure your endpoint returns a 2xx status code
4. Check for firewall or network issues blocking Svix's IP addresses

### Signature Verification Failing

1. Verify you're using the correct webhook secret from the Svix dashboard
2. Ensure you're passing the raw request body to the verification function
3. Check that all required headers (`svix-id`, `svix-timestamp`, `svix-signature`) are present
4. Make sure you're using the latest version of the Svix SDK

### Webhook Timeouts

1. Reduce processing time in your webhook handler
2. Return a 2xx response immediately and process asynchronously
3. Check for slow database queries or external API calls
4. Consider using a message queue for heavy processing

## Comparison with Direct Webhooks

| Feature            | Svix Webhooks                     | Direct Webhooks            |
| ------------------ | --------------------------------- | -------------------------- |
| Setup complexity   | Requires Svix configuration       | Simple URL configuration   |
| Security           | Built-in request signing          | Manual validation required |
| Retry logic        | Advanced with exponential backoff | Basic (3 retries)          |
| Delivery tracking  | Full dashboard with logs          | Not included               |
| Multiple endpoints | Supported via channels            | One URL per job            |
| Replay capability  | Built-in via dashboard            | Not available              |
| Debugging tools    | Comprehensive dashboard           | Manual logging required    |

For production applications with high reliability requirements, **Svix webhooks are recommended** due to their superior security, reliability, and debugging capabilities. For simpler use cases or quick prototyping, consider [direct webhooks](/workflows/direct-webhooks).
