Webhooks notify your application when flow executions complete, eliminating the need for polling.
How Webhooks Work
- You provide a
webhookUrl when starting an execution
- Doclo sends an HTTP POST to your URL when the execution completes
- Your server verifies the signature and processes the result
// 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:
interface WebhookEvent<T = unknown> {
event: 'execution.completed' | 'execution.failed';
timestamp: string; // ISO 8601 format
data: Execution<T>;
}
Example payload:
{
"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.
X-Doclo-Signature: sha256=abc123...
Verification with SDK
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:
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
// 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
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
Always return a 200 response quickly. Process webhooks asynchronously (e.g., queue the work) if processing takes time.
Timestamp Validation
The SDK validates webhook timestamps by default to prevent replay attacks:
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
Never commit your webhook secret to version control.
Best practices:
- Always verify signatures in production
- Use HTTPS for your webhook endpoint
- Validate timestamps to prevent replay attacks
- Return 200 quickly and process asynchronously
- Store the secret securely in environment variables
Get Your Webhook Secret
- Log in to app.doclo.ai
- Navigate to Settings → Webhooks
- Copy your webhook signing secret
- Store it as
DOCLO_WEBHOOK_SECRET in your environment
Next Steps