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

# Webhooks

> Receive execution results via HTTP callbacks

Webhooks notify your application when flow executions complete, eliminating the need for polling.

## How Webhooks Work

1. You provide a `webhookUrl` when starting an execution
2. Doclo sends an HTTP POST to your URL when the execution completes
3. Your server verifies the signature and processes the result

```typescript theme={null}
// Start execution with webhook
const execution = await client.flows.run('flow_abc123', {
  input: {
    document: { base64, filename, mimeType: 'application/pdf' }
  },
  webhookUrl: 'https://your-app.com/api/doclo-webhook'
});

// Execution runs asynchronously
// Your webhook receives the result when complete
```

## Webhook Payload

Doclo sends a JSON payload with the execution result:

```typescript theme={null}
interface WebhookEvent<T = unknown> {
  event: 'execution.completed' | 'execution.failed';
  timestamp: string;  // ISO 8601 format
  data: Execution<T>;
}
```

Example payload:

```json theme={null}
{
  "event": "execution.completed",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "id": "exec_abc123",
    "flowId": "flow_xyz789",
    "status": "success",
    "createdAt": "2024-01-15T10:29:55.000Z",
    "completedAt": "2024-01-15T10:30:00.000Z",
    "duration": 5000,
    "output": {
      "invoiceNumber": "INV-2024-001",
      "totalAmount": 1250.00
    },
    "metrics": {
      "tokensUsed": 1500,
      "cost": 0.0023,
      "stepsRun": 2,
      "stepsTotal": 2
    }
  }
}
```

## Signature Verification

Webhooks include a signature header to verify authenticity. Always verify signatures in production.

### Header Format

```
X-Doclo-Signature: sha256=abc123...
```

### Verification with SDK

```typescript theme={null}
import { verifyWebhookSignature, parseWebhookEvent } from '@doclo/client';

app.post('/api/doclo-webhook', async (req, res) => {
  const signature = req.headers['x-doclo-signature'] as string;

  // Verify signature (requires raw body)
  const isValid = await verifyWebhookSignature(
    req.rawBody,  // Raw request body as string or Buffer
    signature,
    process.env.DOCLO_WEBHOOK_SECRET!
  );

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Parse and validate the event
  const event = parseWebhookEvent(req.body);

  if (event.event === 'execution.completed') {
    console.log('Extraction complete:', event.data.output);
  } else if (event.event === 'execution.failed') {
    console.error('Extraction failed:', event.data.error);
  }

  res.status(200).json({ received: true });
});
```

### Manual Verification

If you need to verify without the SDK:

```typescript theme={null}
import { createHmac } from 'crypto';

function verifySignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const [algorithm, providedHash] = signature.split('=');

  if (algorithm !== 'sha256') {
    return false;
  }

  const expectedHash = createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(providedHash),
    Buffer.from(expectedHash)
  );
}
```

## Framework Examples

### Next.js API Route

```typescript theme={null}
// app/api/doclo-webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifyWebhookSignature, parseWebhookEvent } from '@doclo/client';

export async function POST(request: NextRequest) {
  const rawBody = await request.text();
  const signature = request.headers.get('x-doclo-signature');

  if (!signature) {
    return NextResponse.json(
      { error: 'Missing signature' },
      { status: 401 }
    );
  }

  const isValid = await verifyWebhookSignature(
    rawBody,
    signature,
    process.env.DOCLO_WEBHOOK_SECRET!
  );

  if (!isValid) {
    return NextResponse.json(
      { error: 'Invalid signature' },
      { status: 401 }
    );
  }

  const body = JSON.parse(rawBody);
  const event = parseWebhookEvent(body);

  // Process the event
  if (event.event === 'execution.completed') {
    await processResult(event.data);
  }

  return NextResponse.json({ received: true });
}
```

### Express

```typescript theme={null}
import express from 'express';
import { verifyWebhookSignature, parseWebhookEvent } from '@doclo/client';

const app = express();

// Use raw body for signature verification
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook', async (req, res) => {
  const signature = req.headers['x-doclo-signature'] as string;

  const isValid = await verifyWebhookSignature(
    req.body,
    signature,
    process.env.DOCLO_WEBHOOK_SECRET!
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const event = parseWebhookEvent(JSON.parse(req.body.toString()));

  // Process event...

  res.status(200).send('OK');
});
```

## Event Types

| Event                 | Description                     |
| --------------------- | ------------------------------- |
| `execution.completed` | Execution finished successfully |
| `execution.failed`    | Execution failed with error     |

## Retry Behavior

Doclo retries failed webhook deliveries:

| Attempt | Delay      |
| ------- | ---------- |
| 1       | Immediate  |
| 2       | 1 minute   |
| 3       | 5 minutes  |
| 4       | 30 minutes |
| 5       | 2 hours    |

A delivery is considered failed if:

* Your server returns a non-2xx status code
* Connection times out (30 seconds)
* Connection cannot be established

<Tip>
  Always return a 200 response quickly. Process webhooks asynchronously (e.g., queue the work) if processing takes time.
</Tip>

## Timestamp Validation

The SDK validates webhook timestamps by default to prevent replay attacks:

```typescript theme={null}
const event = parseWebhookEvent(body, {
  maxAgeSeconds: 300  // Reject webhooks older than 5 minutes (default)
});
```

If a webhook is too old, `parseWebhookEvent` throws an error.

## Webhook Security

<Warning>
  Never commit your webhook secret to version control.
</Warning>

Best practices:

1. **Always verify signatures** in production
2. **Use HTTPS** for your webhook endpoint
3. **Validate timestamps** to prevent replay attacks
4. **Return 200 quickly** and process asynchronously
5. **Store the secret securely** in environment variables

## Get Your Webhook Secret

1. Log in to [app.doclo.ai](https://app.doclo.ai)
2. Navigate to Settings → Webhooks
3. Copy your webhook signing secret
4. Store it as `DOCLO_WEBHOOK_SECRET` in your environment

## Next Steps

<CardGroup cols={2}>
  <Card title="Executing Flows" icon="play" href="/cloud/executing-flows">
    Learn about execution options
  </Card>

  <Card title="Authentication" icon="key" href="/cloud/authentication">
    API key management
  </Card>
</CardGroup>
