Setup & Testing
This guide walks you through creating a webhook endpoint in the Ocriva dashboard, verifying signatures in your application code, testing deliveries locally, and reading webhook logs to debug failures.
Creating a Webhook Endpoint
Step 1 — Navigate to Webhooks
Go to your Organization Settings and select the Webhooks tab. Click Add Endpoint in the top-right corner.
Step 2 — Configure the Endpoint
Fill in the endpoint form:
| Field | Required | Description |
|---|---|---|
| Name | Yes | A human-readable label, e.g. Production ERP Receiver |
| URL | Yes | The full URL Ocriva will POST to, e.g. https://api.yourapp.com/webhooks/ocriva |
| Events | Yes | Select one or more event types to subscribe to |
| Secret | No | A random string used to sign deliveries (strongly recommended) |
| Custom Headers | No | Additional HTTP headers to include in every delivery |
| Description | No | Notes for your team |
Step 3 — Select Events
Choose the events this endpoint should receive. You can subscribe to any combination:
document.uploaded— file received, before processingdocument.processed— processing succeeded with extracted datadocument.failed— processing failedbatch.uploaded— batch of files receivedbatch.completed— all files in a batch finishedtemplate.created/template.updated/template.deleted— template lifecycle
Step 4 — Save and Verify
Click Save. The endpoint status starts as active. Ocriva will begin delivering events immediately. You can pause or deactivate the endpoint at any time from the endpoint settings.
IMPORTANT
Always verify webhook signatures in production. Without signature verification, any server that knows your endpoint URL can send fake webhook payloads to your system. A secret takes 30 seconds to add and completely eliminates this risk.
Signature Verification
When you set a secret on an endpoint, Ocriva computes an HMAC-SHA256 digest of the raw request body using your secret as the key and includes it in the X-Webhook-Signature header as sha256={hex_digest}.
Your server should:
- Read the raw request body as bytes — do not parse JSON first
- Compute
HMAC-SHA256(body, secret)using the same secret - Compare your computed digest to the value in
X-Webhook-Signature - Reject the request (return
401) if they do not match
Node.js Verification Example
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(body))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Express.js Middleware Example
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body parser for the webhook route — do NOT use express.json() here
app.post('/webhooks/ocriva', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.OCRIVA_WEBHOOK_SECRET;
if (!signature || !secret) {
return res.status(401).json({ error: 'Missing signature or secret' });
}
const expectedSig = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
const sigBuffer = Buffer.from(signature.replace('sha256=', ''));
const expectedBuffer = Buffer.from(expectedSig);
if (sigBuffer.length !== expectedBuffer.length ||
!crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Signature is valid — parse and handle the event
const event = JSON.parse(req.body.toString());
switch (event.eventType) {
case 'document.processed':
// handle extracted data
break;
case 'document.failed':
// handle failure
break;
default:
// ignore unknown events
}
// Always respond with 2xx quickly
res.status(200).json({ received: true });
});WARNING
Your endpoint must respond with a 2xx status code within the configured timeout (default 30 seconds). If your processing logic takes longer than that — for example, writing to a database, calling an external API, or running heavy computation — return 200 immediately and hand the work off to a background queue. Slow responses cause delivery failures and unnecessary retries.
Retry Policy
If Ocriva does not receive a 2xx response — or the request times out — it retries automatically:
| Attempt | Delay |
|---|---|
| 1st attempt | Immediate |
| 2nd attempt | ~30 seconds after the 1st failure |
| 3rd attempt | ~5 minutes after the 2nd failure |
After 3 failed attempts, the delivery is marked as failed in the logs. No further retries are made for that event. You can review failed deliveries in the webhook logs and manually re-trigger if needed.
What counts as success: any HTTP response with a 2xx status code (200–299).
What counts as failure: any non-2xx status, connection refused, DNS error, or timeout.
Testing Webhooks
Manual Trigger via API
You can send a test event to any endpoint without waiting for real activity using the trigger endpoint:
curl -X POST https://api.ocriva.com/webhooks/{organizationId}/trigger \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"endpointId": "wh_endpoint_id",
"eventType": "document.processed"
}'This sends a sample payload for the specified event type to your endpoint and logs the delivery result.
Local Development with ngrok
When developing locally, your localhost server is not reachable by Ocriva. Use ngrok (or a similar tunnel tool) to expose your local port to the internet:
# Install ngrok from https://ngrok.com, then:
ngrok http 3000ngrok gives you a public HTTPS URL like https://abc123.ngrok.io. Use that as your webhook endpoint URL in Ocriva during development.
TIP
ngrok's web inspector (available at http://localhost:4040 while ngrok is running) shows every request and response in real time. You can replay any request with one click — useful when iterating on your handler logic without re-triggering events.
Verifying Test Deliveries
After triggering a test:
- Check your application logs — confirm the request arrived and was handled correctly.
- Open the Webhook Logs tab in the Ocriva dashboard — find the delivery and confirm it shows a
200status. - If the delivery failed, expand the log entry to see the full error and response body.
Webhook Logs
Every delivery attempt is stored in the webhook logs. To view them:
- Go to Organization Settings → Webhooks
- Click on an endpoint
- Select the Logs tab
Each log entry shows:
| Column | Description |
|---|---|
| Timestamp | When the delivery was attempted |
| Event Type | The event that triggered the delivery |
| Status | success or failed |
| HTTP Status | The response code returned by your server |
| Response Time | How long your server took to respond (ms) |
| Error | Error message if the delivery failed |
You can also retrieve logs via the API:
GET /webhooks/{organizationId}/logsAnd endpoint-level statistics (total requests, success rate, average response time):
GET /webhooks/{organizationId}/statsBest Practices
Respond Immediately, Process Later
Your endpoint should return 200 OK as fast as possible — ideally under 1 second. Do not run database writes, external API calls, or heavy computation synchronously inside the webhook handler. Instead, push the event to a queue (BullMQ, SQS, Redis, etc.) and process it asynchronously.
app.post('/webhooks/ocriva', express.raw({ type: 'application/json' }), async (req, res) => {
// Verify signature first (fast)
if (!verifySignature(req)) return res.status(401).end();
const event = JSON.parse(req.body.toString());
// Enqueue for background processing (fast)
await queue.add('webhook-event', event);
// Respond immediately
res.status(200).json({ received: true });
});Handle Duplicate Events
Due to retries and network conditions, your endpoint may occasionally receive the same event more than once. Always implement idempotent handlers by checking whether you have already processed a given eventId before taking action.
async function handleDocumentProcessed(event) {
const alreadyProcessed = await db.events.findOne({ eventId: event.eventId });
if (alreadyProcessed) return; // skip duplicate
await db.events.insert({ eventId: event.eventId, processedAt: new Date() });
// ... rest of handler
}Always Verify Signatures in Production
Never skip signature verification in production. A single line of middleware protects your system from spoofed events and unintended side effects.
Use Descriptive Endpoint Names
Name your endpoints after their purpose and environment: Production — ERP Invoice Receiver, Staging — Slack Notifier. This makes the logs much easier to read when something goes wrong.
Monitor Failure Rates
Check webhook stats regularly. A rising failure rate on an endpoint often indicates a problem with your server before it becomes a larger incident.
