---
title: "Register a new webhook"
method: POST
path: "/webhooks"
tags: ["webhooks"]
---

# Register a new webhook

`POST /webhooks`

## Request

- Base URL: `https://docs-api.formify.eu/v1`
- URL: `https://docs-api.formify.eu/v1/webhooks`
- Auth: OAuth 2 (authorization code, authorization URL `https://docs-api.formify.eu/v1/oauth/authorize`, token URL `https://docs-api.formify.eu/v1/oauth/token`)

Register a new webhook to receive event notifications.

### About Webhooks
Webhooks allow Formify to communicate with your system using callbacks triggered by specified Formify events. Webhooks provide real-time data updates, enabling you to integrate Formify events directly into your workflows.

#### What’s the use for webhooks?
With webhooks, you can be notified of specific events to automate workflows such as sending documents for signature or tracking their status in real-time.

#### How do I set up webhooks?
When you create a webhook, specify the events you'd like to subscribe to. To reduce unnecessary requests to your server, only subscribe to the events you plan to handle.

### Entity Types
Formify uses entity types to filter webhook events based on the scope of data you want to receive. The entity type defines which object changes in Formify trigger your webhook.

| Entity Type       | Description                                                                                                 | Example                                                               |
|-------------------|-------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|
| **Document**      | Subscribe to events involving a specific document.                                                          | A document is sent for signature, signed, or completed (sealed). Each change triggers your webhook. |

### Supported Events
- `document.created`: A document has been created/sent for signature.
- `document.opened`: A document has been opened.
- `document.signed`: A document has been signed by a signee.
- `document.completed`: A document process has been signed by all parties and is sealed.
- `document.revoked`: A document has been revoked.
- `document.deleted`: A document has been deleted.

### Notes
- Other entity types (e.g., `settings.x`) are not yet supported.
- Do not assume ordering: Events may arrive late, more than once, or out of order

### Webhook Request Payload          
- See the `DocumentWebhookEvent` schema for the structure of the Document entity type payload.

#### Example for General Events (`document.created`, `document.opened`, etc.)
```json
{
  "event": {
    "eventId": "123e4567-e89b-12d3-a456-426614174000",
    "type": "document.created",
    "date": "2024-10-25T14:23:55Z",
    "data": {
      "documentId": "789e4567-e89b-12d3-a456-426614174111",
      "accountId": "456e4567-e89b-12d3-a456-426614174222",
      "userId": "123e4567-e89b-12d3-a456-426614174333"
    }
  }
}
```

- **eventId**: The unique identifier for the event instance.
- **type**: The type of the event that triggered the webhook.
- **date**: The date and time the event was triggered in ISO 8601 format.
- **data**: Contains the `documentId`, `userId` and `accountId` relevant to the event.

#### Example for `document.signed` Event
```json
{
  "event": {
    "eventId": "123e4567-e89b-12d3-a456-426614174000",
    "type": "document.signed",
    "date": "2024-10-25T14:23:55Z",
    "data": {
      "documentId": "789e4567-e89b-12d3-a456-426614174111",
      "accountId": "456e4567-e89b-12d3-a456-426614174222",
      "userId": "123e4567-e89b-12d3-a456-426614174333",
      "signeeId": "789e4567-e89b-12d3-a456-426614174555"
    }
  }
}
```

### Notes
- All event payloads include `eventId`, `type`, `date`, and a `data` object with event-specific details.
- The data object may differ between events and the `signeeId` field is only included for the `document.signed` event.

### Expected Response for Webhooks
To acknowledge receipt of the webhook, your server should respond with an HTTP status code in the 2xx range. No specific content is required in the response body; a `200 OK` status alone will indicate successful processing.

#### Example Response
```http
HTTP/1.1 200 OK
Content-Type: application/json
```

### Retry Attempts
Formify will attempt to deliver webhook events up to 5 times if the initial attempt fails. The retry intervals* are as follows:
- First retry: "immediately"
- Second retry: after ~5 minutes
- Third retry: after ~30 minutes 
- Fourth retry: after ~2 hours
- Fifth retry: after ~24 hours

*The intervals are not exact and may vary slightly. After 5 retries, the event will not be sent again.

Formify must receive a success status code (2xx) from your server to acknowledge the event. If a success status code is not received, Formify will retry sending the event according to the intervals specified above.

Each event sent by Formify includes a unique `eventId`. You can use this `eventId` to ensure that your system processes each event only once and to avoid handling duplicate events.

### Security and Reliability

To secure your webhook endpoint:

- **Use HTTPS**: Always use HTTPS URLs for your webhook endpoint to encrypt data in transit
- **Verify webhook signatures**: Use HMAC-SHA256 to verify that requests are authentic (see Webhook Signature Verification below)
- **Use eventId for idempotency**: Store processed `eventId` values to safely ignore duplicate deliveries

### Webhook Delivery Headers

Every webhook delivery includes the following headers:

| Header | Type | Description |
|---|---|---|
| `Content-Type` | string | Always `application/json` |
| `X-Formify-Timestamp` | string | Unix timestamp (seconds) of when the event was sent |
| `X-Formify-Signature` | string | Lowercase hexadecimal HMAC-SHA256 digest of `{timestamp}.{raw_body}` |

### Webhook Signature Verification

Formify signs all webhook payloads using HMAC-SHA256 so you can verify that requests are authentic. Each webhook endpoint is assigned a unique signing secret (prefixed with `whsec_`) upon creation.

**How to verify:**

The signature is computed over the string `{timestamp}.{raw_body}` using your webhook's signing secret as the HMAC key.

**Example (Node.js with Express):**
```javascript
const crypto = require('crypto');
const express = require('express');

const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.headers['x-formify-timestamp'];
  const signature = req.headers['x-formify-signature'];
  const secret = 'whsec_...'; // your signing secret

  const signedPayload = `${timestamp}.${req.body}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  const isValid = crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(signature)
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);

  // Process the event...
  res.status(200).send('OK');
});
```

**Important:** Use the raw request body exactly as received when verifying the signature. Do not verify against a re-serialized JSON payload.

**Signature format:** The `X-Formify-Signature` header contains the lowercase hexadecimal HMAC-SHA256 digest of:

```
{timestamp}.{raw_body}
```

**Timestamp verification:** You should also verify that the `X-Formify-Timestamp` value is recent to reduce replay attacks.

### Testing Webhooks

For local development, you can use tools such as [ngrok](https://ngrok.com) to expose your local server to the internet and receive webhook calls from Formify.

Example:

    ngrok http 3000

This gives you a public HTTPS URL that you can register as your webhook endpoint for testing.

You can also use request inspection tools such as:

- [webhook.site](https://webhook.site) — Inspect webhook payloads without writing code
- [requestbin.com](https://requestbin.com) — Capture and inspect HTTP requests

These tools are useful for development and debugging before you build your own webhook handler.

**Typical test flow**
1. Register a webhook endpoint
2. Trigger an event, for example by creating or signing a document
3. Verify that the webhook payload is received
4. Return a `2xx` response from your server

> **Note:** These tools are useful for development and debugging. Production webhook endpoints should use your own secure, publicly accessible HTTPS endpoint.

### Idempotency

Webhooks may be delivered more than once due to network issues or retries. To handle this:

```javascript
// Example: Node.js/Express webhook handler
app.post('/webhook', async (req, res) => {
  const { event } = req.body;
  const eventId = event.eventId;

  // Check if we've already processed this event
  const alreadyProcessed = await db.checkEventId(eventId);
  if (alreadyProcessed) {
    console.log(`Event ${eventId} already processed, skipping`);
    return res.status(200).send('OK');
  }

  // Store eventId to prevent duplicate processing
  await db.storeEventId(eventId);

  // Process the event
  await handleEvent(event);

  res.status(200).send('OK');
});
```

### Debugging

**Common Issues:**

| Issue | Cause | Solution |
|-------|-------|----------|
| Webhook not received | URL incorrect or server down | Verify URL is accessible from the internet |
| Webhook retrying repeatedly | Server not returning 2xx status | Check server logs, ensure 200/201/204 response |
| Duplicate events | Not tracking eventId | Implement idempotency using eventId |
| Events out of order | Network delays | Don't rely on event order; use timestamps |

**Logging:**
- Log all incoming webhook requests with timestamp, eventId, and event type
- Log your server's response status code

**Monitoring:**
- Track webhook delivery success rate in Formify dashboard (if available)
- Set up alerts if webhook endpoint returns errors consistently
- Monitor processing time to ensure webhooks are handled quickly

## Request body

- object
  - `webhookUrl` string, uri: The URL where webhook events will be sent.
  - `name` string: The name/alias of the webhook for you to identify it.
  - `eventTypes` string[]: A list of event types that the webhook will be subscribed to.

## Response `201`

Webhook registered successfully.

- object
  - `webhookId` string, uuid: The unique identifier of the registered webhook.
  - `eventTypes` string[]: A list of event types the webhook is subscribed to.
  - `signingSecret` string: HMAC signing secret for this webhook. Store this securely — it is used to verify webhook payloads. Prefixed with `whsec_`.

## Other responses

- `401`: Unauthorized - Missing or invalid access token.
- `500`: Internal server error.

---

[API](https://skmtc.dev/formify/apis/formify-api.md) · [All operations](https://skmtc.dev/formify/apis/formify-api/llms.txt) · [OpenAPI document](https://skmtc.dev/formify/apis/formify-api/revisions/571c4c0d4f4f?raw)
