---
title: Webhooks
description: Receive real-time notifications for document lifecycle events
---

# Webhooks

Webhooks allow your application to receive real-time HTTP notifications when events occur in your Seal account. Instead of polling the API, webhooks push event data to your server immediately.

## How Webhooks Work

1. **Configure Endpoint**: Create a webhook endpoint with your HTTPS URL
2. **Subscribe to Events**: Choose which event types to receive
3. **Receive Notifications**: Seal sends HTTP POST requests to your URL when events occur
4. **Verify Signatures**: Validate webhook authenticity using HMAC-SHA256 signatures
5. **Respond Quickly**: Return 200 OK within 5 seconds to acknowledge receipt

## Available Events

Seal supports 17 webhook event types across three categories:

### Document Events (7 events)

| Event                | Description                           |
| -------------------- | ------------------------------------- |
| `document.created`   | Document was created                  |
| `document.sent`      | Document was sent to recipients       |
| `document.viewed`    | Document was viewed by any recipient  |
| `document.completed` | All recipients completed signing      |
| `document.voided`    | Document was cancelled                |
| `document.expired`   | Document passed its deadline unsigned |
| `document.declined`  | A recipient declined to sign          |

### Recipient Events (6 events)

| Event                | Description                     |
| -------------------- | ------------------------------- |
| `recipient.added`    | Recipient was added to document |
| `recipient.viewed`   | Recipient viewed the document   |
| `recipient.signed`   | Recipient signed the document   |
| `recipient.approved` | Recipient approved the document |
| `recipient.declined` | Recipient declined to sign      |
| `recipient.reminded` | Reminder was sent to recipient  |

### Template Events (3 events)

| Event              | Description                        |
| ------------------ | ---------------------------------- |
| `template.created` | Template was created               |
| `template.updated` | Template was modified              |
| `template.used`    | Document was created from template |

## Webhook Payload Structure

All webhook payloads follow this structure:

```json
{
  "id": "evt_1710288000000_a1b2c3d4",
  "type": "document.completed",
  "api_version": "2025-01-01",
  "created_at": "2026-03-12T12:00:00Z",
  "organization_id": "org_abc123",
  "data": {
    "document_id": "doc_xyz789",
    "title": "Service Agreement",
    "status": "completed",
    "recipients_count": 2,
    "signed_count": 2
  }
}
```

### Payload Fields

| Field             | Type   | Description                                          |
| ----------------- | ------ | ---------------------------------------------------- |
| `id`              | string | Unique event identifier (`evt_{timestamp}_{random}`) |
| `type`            | string | Event type (e.g., `document.completed`)              |
| `api_version`     | string | API version for this payload (`2025-01-01`)          |
| `created_at`      | string | ISO 8601 timestamp when event occurred               |
| `organization_id` | string | Organization that owns this event                    |
| `data`            | object | Event-specific data payload                          |

## Quick Start

### 1. Create Webhook Endpoint

```typescript
const response = await fetch("https://api.seal.nyc/api/v1/webhooks", {
  method: "POST",
  headers: {
    Authorization: "Bearer ak_your_api_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://api.example.com/webhooks/seal",
    events: ["document.completed", "recipient.signed"],
  }),
});

const { secret } = await response.json();
// Save this secret securely - you'll need it for verification
```

### 2. Handle Webhook Requests

```typescript
import { createHmac } from "crypto";

app.post("/webhooks/seal", async (req, res) => {
  // Verify signature
  const signature = req.headers["x-seal-signature"];
  const eventId = req.headers["x-seal-event-id"];
  const timestamp = req.headers["x-seal-timestamp"];
  const payload = JSON.stringify(req.body);

  const signedContent = `${eventId}.${timestamp}.${payload}`;
  const expectedSignature = `v1=${createHmac(
    "sha256",
    process.env.WEBHOOK_SECRET
  )
    .update(signedContent)
    .digest("hex")}`;

  if (signature !== expectedSignature) {
    return res.status(401).send("Invalid signature");
  }

  // Process event
  const event = req.body;

  switch (event.type) {
    case "document.completed":
      await handleDocumentCompleted(event.data);
      break;
    case "recipient.signed":
      await handleRecipientSigned(event.data);
      break;
  }

  // Respond quickly
  res.status(200).send("OK");
});
```

## Best Practices

> **Info**
>
> **Respond Quickly**: Return 200 OK within 5 seconds. Process events
> asynchronously to avoid timeouts.

1. **Always Verify Signatures**: Never trust webhook data without signature verification
2. **Use HTTPS**: Webhook URLs must use HTTPS in production
3. **Handle Idempotency**: Events may be delivered more than once - use `event.id` to deduplicate
4. **Return 200 Quickly**: Acknowledge receipt immediately, process asynchronously
5. **Monitor Failures**: Check webhook delivery logs for failed deliveries
6. **Rotate Secrets**: Rotate webhook secrets every 90 days

## Retry Behavior

If your endpoint doesn't respond with 200 OK, Seal will retry delivery:

- **Retry Schedule**: 30 seconds, 2 minutes, 10 minutes, 30 minutes, 2 hours
- **Maximum Attempts**: 5 total attempts
- **Exponential Backoff**: Increasing delays between retries
- **Abandonment**: After 5 failed attempts, delivery is abandoned
- **Auto-Disable**: After 10 consecutive endpoint failures, the endpoint is automatically disabled

## Testing Webhooks

### Local Development

Use tools like [ngrok](https://ngrok.com) to expose your local server:

```bash
ngrok http 3000
# Use the HTTPS URL: https://abc123.ngrok.io/webhooks/seal
```

### Manual Testing

Send test events from the Seal dashboard or use the API:

```typescript
const response = await fetch(
  "https://api.seal.nyc/api/v1/webhooks/wh_abc123/test",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer ak_your_api_key_here",
    },
  }
);
```

## Next Steps

- **[Webhook Setup](/docs/webhooks/setup)** - Detailed configuration guide
- **[Signature Verification](/docs/webhooks/verification)** - Security implementation
- **[Webhooks API](/docs/api-reference/webhooks)** - API reference
