Skip to Content
GuidesWebhooks

Webhooks

Webhooks allow your application to receive real-time notifications 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

Creating a Webhook

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

Save the webhook secret immediately - it’s only shown once! You’ll need it to verify webhook signatures.

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. Always verify signatures to ensure authenticity.

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 automatically retries failed webhook deliveries:

AttemptDelay
1Immediate
21 minute
35 minutes
415 minutes
51 hour

A delivery is considered 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. Always verify signatures - Never process unverified webhooks
  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