Ocriva Logo

Documents

Setup & Testing

Set up webhook endpoints, configure events, verify signatures, and test delivery.

webhookssetuptestingsecurity

Published: 3/31/2026

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:

FieldRequiredDescription
NameYesA human-readable label, e.g. Production ERP Receiver
URLYesThe full URL Ocriva will POST to, e.g. https://api.yourapp.com/webhooks/ocriva
EventsYesSelect one or more event types to subscribe to
SecretNoA random string used to sign deliveries (strongly recommended)
Custom HeadersNoAdditional HTTP headers to include in every delivery
DescriptionNoNotes 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 processing
  • document.processed — processing succeeded with extracted data
  • document.failed — processing failed
  • batch.uploaded — batch of files received
  • batch.completed — all files in a batch finished
  • template.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:

  1. Read the raw request body as bytes — do not parse JSON first
  2. Compute HMAC-SHA256(body, secret) using the same secret
  3. Compare your computed digest to the value in X-Webhook-Signature
  4. 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:

AttemptDelay
1st attemptImmediate
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 3000

ngrok 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:

  1. Check your application logs — confirm the request arrived and was handled correctly.
  2. Open the Webhook Logs tab in the Ocriva dashboard — find the delivery and confirm it shows a 200 status.
  3. 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:

  1. Go to Organization SettingsWebhooks
  2. Click on an endpoint
  3. Select the Logs tab

Each log entry shows:

ColumnDescription
TimestampWhen the delivery was attempted
Event TypeThe event that triggered the delivery
Statussuccess or failed
HTTP StatusThe response code returned by your server
Response TimeHow long your server took to respond (ms)
ErrorError message if the delivery failed

You can also retrieve logs via the API:

GET /webhooks/{organizationId}/logs

And endpoint-level statistics (total requests, success rate, average response time):

GET /webhooks/{organizationId}/stats

Best 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.