Build a webhook endpoint

Write the code that properly handles webhook notifications.

The first step to adding webhooks to your PayEngine 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.

If the endpoint does not return within 3 seconds, it is recorded as a failed delivery attempt.

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.

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')
    }
});

Last updated