# Example webhook handler

Use this example as a boilerplate for your webhook handler. We added extra comments to make it super clear:

{% tabs %}
{% tab title="Node.js" %}

```javascript
const express = require('express')
const router = express.Router()
const crypto = require('crypto')
const { logger } = global

// Create your signing key at https://app.frontitude.com/settings/integrations in the "Webhooks" section
const WEBHOOKS_SIGNING_KEY = process.env.WEBHOOKS_SIGNING_KEY || 'xxx'

router.post('/handle_frontitude_events', async function(req, res) {
    // It is important to disable CSRF protection for this endpoint if the framework you use enables them by default.
    
    const { 'svix-id': webhookId, 'svix-timestamp': webhookTimestamp, 'svix-signature': webhookSignature } = req.headers
    const { eventType, data } = req.body

    logger.info({
        message: 'Frontitude webhooks: new event received',
        eventType,
        webhookId
    })

    // Compare event's timestamp (in seconds since epoch) against system's timestamp to make sure it's within tolerance in order to prevent timestamp/replay attacks (see: https://en.wikipedia.org/wiki/Replay_attack)
    const VALID_TIMESTAMP_TOLERANCE_IN_MINUTES = 5
    const currentTimestampInSeconds = Math.floor(new Date().getTime() / 1000)
    const minutesAgo = currentTimestampInSeconds - (1000 * 60 * VALID_TIMESTAMP_TOLERANCE_IN_MINUTES)
    const minutesLater = currentTimestampInSeconds + (1000 * 60 * VALID_TIMESTAMP_TOLERANCE_IN_MINUTES)
    if (webhookTimestamp < minutesAgo || webhookTimestamp > minutesLater) {
        logger.error({
            message: 'Frontitude webhooks: invalid timestamp',
            webhookTimestamp,
            minutesAgo,
            minutesLater,
            request: {
                headers: req.headers,
                body: req.body
            }
        })

        return res.status(401).send('Invalid timestamp')
    }

    // Verify event's signature by comparing the signature sent in the webhook headers with the signature computed from the webhook payload
    // The content to sign is composed by concatenating the webhook id, timestamp and payload, separated by the full-stop character (.).
    signedContent = `${webhookId}.${webhookTimestamp}.${JSON.stringify(req.body)}`
    const expectedSignature = crypto
        .createHmac('sha256', Buffer.from(WEBHOOKS_SIGNING_KEY, 'base64'))
        .update(signedContent)
        .digest('base64')
    
    // The expected signature should match one of the signatures sent in the signature header
    // Signature header is composed of a list of space delimited signatures and their corresponding version identifiers. The list is most commonly of length one. Though there could be any number of signatures.
    // For example: "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo="
    const webhookPotentialSignatures = webhookSignature.split(' ').map(signature => signature.split(',')[1])
    if (!webhookPotentialSignatures.includes(expectedSignature)) {
        logger.error({
            message: 'Frontitude webhooks: invalid signature',
            expectedSignature,
            webhookSignature,
            request: {
                headers: req.headers,
                body: req.body
            }
        })

        return res.status(401).send('Invalid signature')
    }

    // Process the event
    // The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (up to 15s). If processing the webhook takes longer than that, it's better to return a 2xx response and process the webhook asynchronously, to avoid Frontitude retrying the webhook delivery.

    logger.info({
        message: 'Frontitude webhooks: processed event successfully',
        eventType,
        data
    })

    res.sendStatus(200)
})

module.exports = router
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developer.frontitude.com/webhooks/example-webhook-handler.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
