Skip to Content
GuidesWebhooks

Webhooks

Webhooks deliver real-time notifications to your application when document events occur.

Event Types

EventDescription
document_createdDocument was created
document_sentDocument was sent for signing
document_viewedA signer opened the document
document_signedA signer completed their signature
document_completedAll signers finished, document is sealed
document_voidedDocument was cancelled
document_declinedA signer declined to sign

Signing packet events

Packet event names use dots to distinguish them from document events:

EventDescription
signing_packet.sentKorala froze the manifest and sent the packet
signing_packet.viewedThe recipient opened the packet
signing_packet.item_signedThe worker signed one packet item
signing_packet.recipient_completedThe recipient signed all packet items
signing_packet.partially_completedSome selected items could not complete
signing_packet.voidedThe sender voided the packet
signing_packet.expiredThe packet passed its expiration time

Document events still fire for packet items. Subscribe to packet events for the aggregate workflow and document events for signed-file readiness. A signing_packet.recipient_completed event can arrive before document_completed when another signer still has a turn on that document.

Creating a Webhook

const webhook = await korala.webhooks.create({ url: 'https://your-app.com/webhooks/korala', events: ['document_completed', 'document_signed'], mode: 'live', // or 'test' for sandbox documents }); console.log(`Webhook ID: ${webhook.id}`); console.log(`Secret: ${webhook.secret}`); // Save this for verification!

Save the webhook secret when you create the webhook; Korala shows it once. You need it to verify webhook signatures.

Test and Live Mode

Every webhook endpoint has a mode, and an endpoint only receives events that match it:

ModeReceives
live (default)Events from real documents
testEvents from sandbox documents

Sandbox activity never reaches a live endpoint, so you can develop against a test endpoint without your production handler seeing the traffic or your production events showing up in a local tunnel while you debug.

Register two endpoints to cover both:

await korala.webhooks.create({ url: 'https://your-app.com/webhooks/korala', events: ['document_completed', 'signing_packet.recipient_completed'], mode: 'live', }); await korala.webhooks.create({ url: 'https://staging.your-app.com/webhooks/korala', events: ['document_completed'], mode: 'test', });

The payload’s data.sandbox field still tells you which kind of document an event came from, so a single handler can serve both endpoints if you prefer.

Session and workflow events have no sandbox equivalent and are always delivered as live traffic.

Webhook Payload

All webhooks follow this format:

{ "eventId": "550e8400-e29b-41d4-a716-446655440000", "event": "document_completed", "timestamp": "2024-01-15T10:30:00Z", "data": { "documentId": "doc_xyz789", "name": "contract.pdf", "completedAt": "2024-01-15T10:30:00Z", "signerCount": 2, "sandbox": false } }

Event-Specific Data

Each event type has a typed data payload. Import the corresponding type from @korala/api-client:

import type { DocumentCreatedEventData, DocumentSentEventData, DocumentViewedEventData, DocumentSignedEventData, DocumentCompletedEventData, DocumentFailedEventData, DocumentVoidedEventData, DocumentDeclinedEventData, TestEventData, } from '@korala/api-client';

document_created

{ "eventId": "550e8400-e29b-41d4-a716-446655440000", "event": "document_created", "timestamp": "2024-01-15T10:30:00Z", "data": { "documentId": "doc_xyz789", "name": "contract.pdf", "status": "draft", "sandbox": false } }

document_sent

{ "eventId": "550e8400-e29b-41d4-a716-446655440000", "event": "document_sent", "timestamp": "2024-01-15T10:30:00Z", "data": { "documentId": "doc_xyz789", "name": "contract.pdf", "signerCount": 2, "sandbox": false } }

document_viewed

{ "eventId": "550e8400-e29b-41d4-a716-446655440000", "event": "document_viewed", "timestamp": "2024-01-15T10:30:00Z", "data": { "documentId": "doc_xyz789", "signerId": "signer_abc123", "signerEmail": "[email protected]", "signerExternalId": "ext_123", "sandbox": false } }

document_signed

{ "eventId": "550e8400-e29b-41d4-a716-446655440000", "event": "document_signed", "timestamp": "2024-01-15T10:30:00Z", "data": { "documentId": "doc_xyz789", "signerId": "signer_abc123", "signerEmail": "[email protected]", "signerExternalId": "ext_123", "allSigned": false, "sandbox": false } }

document_completed

{ "eventId": "550e8400-e29b-41d4-a716-446655440000", "event": "document_completed", "timestamp": "2024-01-15T10:30:00Z", "data": { "documentId": "doc_xyz789", "name": "contract.pdf", "completedAt": "2024-01-15T10:30:00Z", "signerCount": 2, "sandbox": false } }

document_failed

{ "eventId": "550e8400-e29b-41d4-a716-446655440000", "event": "document_failed", "timestamp": "2024-01-15T10:30:00Z", "data": { "documentId": "doc_xyz789", "error": "Signing provider unavailable", "sandbox": false } }

document_voided

{ "eventId": "550e8400-e29b-41d4-a716-446655440000", "event": "document_voided", "timestamp": "2024-01-15T10:30:00Z", "data": { "documentId": "doc_xyz789", "name": "contract.pdf", "sandbox": false } }

document_declined

{ "eventId": "550e8400-e29b-41d4-a716-446655440000", "event": "document_declined", "timestamp": "2024-01-15T10:30:00Z", "data": { "documentId": "doc_xyz789", "signerId": "signer_abc123", "signerEmail": "[email protected]", "signerExternalId": "ext_123", "reason": "Terms not acceptable", "sandbox": false } }

Verifying Signatures

Korala signs all webhook payloads with HMAC-SHA256. Verify the signature on each delivery.

Signature Headers

HeaderDescription
X-SignatureHMAC-SHA256 signature
X-TimestampUnix timestamp when sent
X-Webhook-EventEvent type (e.g. document_signed)

Verification Process

import crypto from 'crypto'; import express from 'express'; const WEBHOOK_SECRET = 'your-webhook-secret'; app.post('/webhooks/korala', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-signature'] as string; const timestamp = req.headers['x-timestamp'] as string; const body = req.body.toString(); // Verify timestamp is within 5 minutes const now = Math.floor(Date.now() / 1000); if (Math.abs(now - parseInt(timestamp)) > 300) { return res.status(401).json({ error: 'Timestamp too old' }); } // Compute expected signature const message = `${timestamp}.${body}`; const expectedSignature = crypto .createHmac('sha256', WEBHOOK_SECRET) .update(message) .digest('hex'); // Compare signatures if (!crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) )) { return res.status(401).json({ error: 'Invalid signature' }); } // Process the webhook const event = JSON.parse(body); console.log(`Received ${event.event} for document ${event.data.documentId}`); // Always respond quickly res.status(200).json({ received: true }); });

Retry Policy

Korala retries failed webhook deliveries:

AttemptDelay
1Immediate
21 minute
35 minutes
415 minutes
51 hour

A delivery counts as failed if:

  • Your endpoint returns a non-2xx status code
  • The request times out (30 seconds)
  • A network error occurs

Managing Webhooks

List Webhooks

const webhooks = await korala.webhooks.list(); for (const webhook of webhooks) { console.log(`${webhook.url}: ${webhook.events.join(', ')}`); }

Update Webhook

await korala.webhooks.update(webhookId, { url: 'https://your-app.com/webhooks/v2/korala', events: ['document_completed'], });

Delete Webhook

await korala.webhooks.delete(webhookId);

View Delivery History

const deliveries = await korala.webhooks.deliveries(webhookId); for (const delivery of deliveries) { console.log(`${delivery.eventType}: ${delivery.status}`); console.log(` Response: ${delivery.responseCode}`); console.log(` Attempts: ${delivery.attemptCount}`); }

Best Practices

  1. Verify signatures - Reject any delivery that fails verification
  2. Respond quickly - Return 200 within 5 seconds, process async
  3. Handle duplicates - Use eventId for idempotency
  4. Use HTTPS - Korala only sends webhooks to HTTPS endpoints
  5. Monitor failures - Set up alerts for repeated delivery failures

Testing Webhooks

Use tools like webhook.site  or ngrok  to test webhooks during development:

# Expose local server ngrok http 3000 # Use the ngrok URL when creating webhooks # https://abc123.ngrok.io/webhooks/korala
Last updated on