Webhooks
Webhooks allow your application to receive real-time notifications when document events occur.
Event Types
| Event | Description |
|---|---|
document_created | Document was created |
document_sent | Document was sent for signing |
document_viewed | A signer opened the document |
document_signed | A signer completed their signature |
document_completed | All signers finished, document is sealed |
document_voided | Document was cancelled |
document_declined | A signer declined to sign |
Creating a Webhook
TypeScript
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
| Header | Description |
|---|---|
X-Signature | HMAC-SHA256 signature |
X-Timestamp | Unix timestamp when sent |
X-Webhook-Event | Event type (e.g. document_signed) |
Verification Process
TypeScript
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:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 15 minutes |
| 5 | 1 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
TypeScript
const webhooks = await korala.webhooks.list();
for (const webhook of webhooks) {
console.log(`${webhook.url}: ${webhook.events.join(', ')}`);
}Update Webhook
TypeScript
await korala.webhooks.update(webhookId, {
url: 'https://your-app.com/webhooks/v2/korala',
events: ['document_completed'],
});Delete Webhook
TypeScript
await korala.webhooks.delete(webhookId);View Delivery History
TypeScript
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
- Always verify signatures - Never process unverified webhooks
- Respond quickly - Return 200 within 5 seconds, process async
- Handle duplicates - Use
eventIdfor idempotency - Use HTTPS - Korala only sends webhooks to HTTPS endpoints
- 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/koralaLast updated on