# Build a webhook endpoint

The first step to adding webhooks to your integration is to build your own custom endpoint. Creating a webhook endpoint on your server is no different from creating any page on your website.

For each event occurrence, we POST the webhook data to your endpoint in JSON format. The full event details are included and can be used after parsing the JSON into an event object. Thus, at minimum, that webhook endpoint needs to expect data through a POST request and confirm successful receipt of that data.

### Return 2xx status code quickly

To acknowledge receipt of an event, your endpoint must return a `2xx` HTTP status code. All response codes outside this range, including a `3xx` code, indicate to us that you did not receive the event.

If we don't receive a `2xx` HTTP status code, the notification attempt is repeated. After several failures to send, we mark the event as failed and stop trying to send it to your endpoint. After several days without receiving any `2xx` HTTP status code responses, we automatically disable your endpoint soon after it unaddressed.

{% hint style="danger" %}
If the endpoint does not return within **3 seconds**, it is recorded as a failed delivery attempt.
{% endhint %}

Because properly acknowledging receipt of the webhook notification is important, your endpoint should return a `2xx` HTTP status code prior to any complex logic that could cause a timeout.

### Test that your endpoint works

As your webhook endpoint is used asynchronously, its failures may not be obvious to you until it's too late. For example, after it's been disabled. Always test that your endpoint works.

{% tabs %}
{% tab title="NodeJS" %}

```javascript
app.post("/webhook", (req, res) => {
    const header = req.header("x-pf-signature");
    const body = req.body;
    const secret = process.env.PF_WEBHOOK_SECRET
    
    const details = header.split(",").reduce(
        (obj, item) => {
            const kv = item.split("=");
            if (kv[0] === "t") {
                obj.timestamp = kv[1];
            }
            if (kv[0] === "s") {
                obj.signature = kv[1];
            }
            return obj;
        }, { timestamp: -1, signature: "" }
    );
    if (!details || details.timestamp === -1) {
        throw new Error('Unable to extract timestamp and signature from header')
    }
    
    const payload = `${details.timestamp}.${JSON.stringify(body)}`
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payload, 'utf-8')
        .digest('hex')
    
    if (details.signature !== expectedSignature) {
        throw new Exception('Signature mismatch')
    }
});
```

{% endtab %}

{% tab title="Sample Payload (JSON)" %}

```javascript
{
  event_uid: <unique_event_id>,
  event: 'MERCHANT_CREATED',
  data: {
    id: <merchant_id>, 
    account_id: <account_id>,
    boarding_gid: <onboarding_id>
  }
}

```

{% 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://docs.payengine.co/developer-docs/webhooks/build-a-webhook-endpoint.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.
