# Setting up webhooks

The [Webhook API](https://docs.ai.insly.com/nora/api-documentation/webhooks) allows you to receive real-time notifications when specific events occur in your FormFlow submissions. By subscribing to webhook events, your application can automatically respond to submission processing milestones without polling.

### Available Event Types

The webhook system supports six event types:

<table><thead><tr><th width="252.2890625">Event</th><th>Triggered When</th></tr></thead><tbody><tr><td><code>extracted</code></td><td>AI extraction of submission data completes successfully</td></tr><tr><td><code>export</code></td><td>Manual export is initiated for a submission</td></tr><tr><td><code>validation-success</code></td><td>Submission validation passes before export</td></tr><tr><td><code>error-export</code></td><td>Triggered when export fails (both automatic and manual exports)</td></tr><tr><td><code>error-processing</code></td><td>Triggered when submission processing/extraction fails</td></tr><tr><td><code>error-pre-processing</code></td><td>Triggered when email submission pre-processing fails</td></tr></tbody></table>

All webhook events share a common payload structure:

```json
{
  "eventId": "123e4567-e89b-12d3-a456-426614174000",
  "eventType": "extracted",
  "timestamp": "2023-10-28T12:00:00.000Z",
  "submissionId": "123e4567-e89b-12d3-a456-426614174001",
  "templateId": 1
}
```

### Webhook Security

When creating a subscription for an event, you will receive a `secret` token which you can use to verify whether the webhook is sent by us and is unchanged.

When we send out webhook events a signature is included in the header. This is created by hashing the payload `secret` before transport. You can use the token to hash the payload on your end using **HMAC-SHA256** (hex-encoding) and compare if the signatures match to confirm that the payload is valid and unchanged.

{% hint style="warning" %}
**Important**: You must use the exact raw request body string to compute the hash, not a re-serialized version of the parsed JSON. Different JSON serialization can produce different strings even for the same data.
{% endhint %}

#### **Signature Headers**

<table><thead><tr><th width="205.23828125">Header</th><th>Description</th><th>Example</th></tr></thead><tbody><tr><td><code>X-Webhook-Signature</code></td><td>HMAC-SHA256 signature (hex-encoded)</td><td><code>a3b2c1d4e5f6...</code></td></tr><tr><td><code>X-Webhook-Timestamp</code></td><td>ISO 8601 timestamp when signature was generated</td><td><code>2023-10-28T12:00:00.000Z</code></td></tr></tbody></table>

### Receiving Webhooks

#### Endpoint Requirements

Your webhook endpoint should:

1. **Respond quickly** - Return a 2xx status code within 5 seconds
2. **Be publicly accessible** - The webhook service must be able to reach your endpoint
3. **Handle idempotency** - The same event may be delivered multiple timesx

Below is an example on how verification works.

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Here we have a separate endpoint for an event, it is also possible to
// create logic that chooses the correct secret based on the "type" field
app.post('/webhooks/formflow/extracted', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  
  
  const secret = process.env.WEBHOOK_SECRET_EXTRACTED;
  
  if (!signature || !verifyWebhookSignature(req.body, signature, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process verified webhook...
});
```
