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

# Local Webhook Testing with ngrok

> Test webhooks locally during development using ngrok

When developing webhook integrations, you need a way to receive webhook events on your local machine. Since DocIntell can't reach `localhost`, you'll need a tunneling service to expose your local server to the internet.

[ngrok](https://ngrok.com) is the most popular tool for this purpose. It creates a secure tunnel from a public URL to your local machine.

## Prerequisites

* A local webhook endpoint running (e.g., on port 3000)
* A DocIntell API key

## Step 1: Install ngrok

<CodeGroup>
  ```bash macOS (Homebrew) theme={null}
  brew install ngrok
  ```

  ```bash Linux (apt) theme={null}
  curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
    | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null \
    && echo "deb https://ngrok-agent.s3.amazonaws.com buster main" \
    | sudo tee /etc/apt/sources.list.d/ngrok.list \
    && sudo apt update \
    && sudo apt install ngrok
  ```

  ```bash Windows (Chocolatey) theme={null}
  choco install ngrok
  ```

  ```bash Direct Download theme={null}
  # Visit https://ngrok.com/download and download for your platform
  ```
</CodeGroup>

## Step 2: Create a Free ngrok Account

1. Sign up at [ngrok.com](https://dashboard.ngrok.com/signup)
2. Get your auth token from the [dashboard](https://dashboard.ngrok.com/get-started/your-authtoken)
3. Configure ngrok with your token:

```bash theme={null}
ngrok config add-authtoken YOUR_AUTH_TOKEN
```

<Note>
  A free ngrok account gives you a random URL that changes each time you restart ngrok. For a stable URL, consider ngrok's paid plans or use the [ngrok free static domain](https://ngrok.com/blog-post/free-static-domains-ngrok-users) feature.
</Note>

## Step 3: Start Your Local Server

First, make sure your webhook endpoint is running locally. Here's a minimal example:

<CodeGroup>
  ```python Python (Flask) theme={null}
  from flask import Flask, request
  import hmac
  import hashlib

  app = Flask(__name__)
  WEBHOOK_SECRET = "whsec_your_secret_here"

  @app.route("/webhooks/docintel", methods=["POST"])
  def handle_webhook():
      # Verify signature
      signature = request.headers.get("X-DocIntell-Signature")
      payload = request.get_data()

      expected = "sha256=" + hmac.new(
          WEBHOOK_SECRET.encode(),
          payload,
          hashlib.sha256
      ).hexdigest()

      if not hmac.compare_digest(signature, expected):
          print("⚠️  Invalid signature!")
          return {"error": "Invalid signature"}, 401

      event = request.get_json()
      print(f"✅ Received event: {event['event']}")
      print(f"   Document ID: {event['data']['document_id']}")

      return {"status": "ok"}, 200

  if __name__ == "__main__":
      app.run(port=3000, debug=True)
  ```

  ```typescript TypeScript (Express) theme={null}
  import express from 'express';
  import crypto from 'crypto';

  const app = express();
  const WEBHOOK_SECRET = 'whsec_your_secret_here';

  app.post('/webhooks/docintel', express.raw({type: '*/*'}), (req, res) => {
    const signature = req.headers['x-docintel-signature'] as string;
    const payload = req.body;

    const expected = 'sha256=' + crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(payload)
      .digest('hex');

    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
      console.log('⚠️  Invalid signature!');
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const event = JSON.parse(payload.toString());
    console.log(`✅ Received event: ${event.event}`);
    console.log(`   Document ID: ${event.data.document_id}`);

    res.json({ status: 'ok' });
  });

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

## Step 4: Start ngrok

In a new terminal, start ngrok pointing to your local server:

```bash theme={null}
ngrok http 3000
```

You'll see output like this:

```
Session Status                online
Account                       your-email@example.com
Version                       3.x.x
Region                        United States (us)
Forwarding                    https://abc123.ngrok-free.app -> http://localhost:3000
```

Copy the `https://` URL (e.g., `https://abc123.ngrok-free.app`) - this is your public webhook URL.

<Tip>
  Keep the ngrok terminal open while testing. If you close it, the tunnel will shut down and you'll need to update your webhook URL.
</Tip>

## Step 5: Register Your ngrok URL with DocIntell

Create a webhook configuration using your ngrok URL:

```bash theme={null}
curl -X POST https://api.docintell.com/v1/webhooks \
  -H "Authorization: Bearer dk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://abc123.ngrok-free.app/webhooks/docintel",
    "events": ["document.uploaded", "document.processing.completed", "document.processing.failed"]
  }'
```

<Warning>
  Save the `signing_secret` from the response! Update your local server's `WEBHOOK_SECRET` variable with this value to verify incoming webhooks.
</Warning>

## Step 6: Test the Integration

Upload a test document to trigger webhook events:

```bash theme={null}
curl -X POST https://api.docintell.com/v1/documents \
  -H "Authorization: Bearer dk_test_YOUR_API_KEY" \
  -F "file=@test.pdf"
```

Watch your local terminal - you should see the webhook events arrive:

```
✅ Received event: document.uploaded
   Document ID: 660e8400-e29b-41d4-a716-446655440001
✅ Received event: document.processing.completed
   Document ID: 660e8400-e29b-41d4-a716-446655440001
```

## Using ngrok's Web Interface

ngrok provides a local web interface for inspecting webhook traffic. Open [http://localhost:4040](http://localhost:4040) in your browser to:

* View all incoming requests
* Inspect request headers and body
* Replay requests for debugging
* See response details

This is invaluable for debugging signature verification issues or understanding payload structure.

## Tips for Development

### Use a Static Domain

If you have an ngrok account, you can claim a free static domain to avoid updating your webhook URL each time:

```bash theme={null}
ngrok http 3000 --domain=your-chosen-name.ngrok-free.app
```

### Multiple Environments

For team development, each developer should:

1. Run their own ngrok tunnel
2. Create their own webhook configuration in DocIntell
3. Use test mode API keys (`dk_test_...`)

### Clean Up Test Webhooks

Remember to delete test webhook configurations when done:

```bash theme={null}
curl -X DELETE https://api.docintell.com/v1/webhooks/{webhook_id} \
  -H "Authorization: Bearer dk_test_YOUR_API_KEY"
```

## Alternatives to ngrok

While ngrok is the most popular option, other tunneling services include:

| Service                                                                                             | Free Tier         | Notes                             |
| --------------------------------------------------------------------------------------------------- | ----------------- | --------------------------------- |
| [ngrok](https://ngrok.com)                                                                          | Yes (random URLs) | Most popular, great debugging UI  |
| [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) | Yes               | Requires Cloudflare account       |
| [localtunnel](https://localtunnel.github.io/www/)                                                   | Yes               | Open source, simpler setup        |
| [Tailscale Funnel](https://tailscale.com/kb/1223/funnel)                                            | Yes               | Good if you already use Tailscale |

## Troubleshooting

<AccordionGroup>
  <Accordion title="ngrok URL not working">
    * Ensure ngrok is still running (check the terminal)
    * Verify your local server is running on the correct port
    * Check that you're using the HTTPS URL, not HTTP
    * Some corporate firewalls block ngrok - try a different network
  </Accordion>

  <Accordion title="Signature verification failing">
    * Make sure you updated `WEBHOOK_SECRET` with the `signing_secret` from the webhook creation response
    * Verify you're using the raw request body, not parsed JSON
    * Check the ngrok web interface (localhost:4040) to see the exact payload being sent
  </Accordion>

  <Accordion title="Webhooks not arriving">
    * Confirm the webhook is registered: `GET /v1/webhooks`
    * Check that `is_active` is `true`
    * Verify you subscribed to the correct events
    * Look at the ngrok web interface for incoming requests
  </Accordion>

  <Accordion title="ngrok session expired">
    Free ngrok sessions expire after a few hours. Simply restart ngrok and update your webhook URL:

    ```bash theme={null}
    curl -X PATCH https://api.docintell.com/v1/webhooks/{webhook_id} \
      -H "Authorization: Bearer dk_test_YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://NEW_URL.ngrok-free.app/webhooks/docintel"}'
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

Once your webhooks are working locally:

* [Deploy your webhook endpoint](/guides/webhook-setup) to production
* Learn about [webhook security](/concepts/webhooks#security) best practices
* Explore [all webhook events](/concepts/webhooks#event-types)
