Add an endpoint
- On Integrations, in Webhooks, choose Add webhook.
- Enter an https address on the public internet. Private network addresses are refused.
- Pick the events you want, such as transaction.created, spend_request.approved or invoice.paid.
- Copy the secret (nf_whsec_...). It is shown once.
Check every message is really from us
Each message has a Neuctra-Signature header with a time and a signature made from your secret. Check it before trusting anything in the body. verifyWebhook does it for you, and also refuses messages older than five minutes, so an old message cannot be replayed.
1import express from "express";
2import { verifyWebhook } from "@neuctra/finance";
3
4const app = express();
5
6// Read the body as text: the signature covers the exact bytes we sent.
7app.post("/neuctra", express.text({ type: "*/*" }), async (req, res) => {
8 try {
9 const event = await verifyWebhook(
10 req.get("Neuctra-Signature"),
11 req.body,
12 process.env.NEUCTRA_WEBHOOK_SECRET,
13 );
14 if (await alreadyHandled(event.id)) return res.sendStatus(200);
15 await handle(event); // event.type, event.data
16 res.sendStatus(200);
17 } catch {
18 res.sendStatus(400);
19 }
20});Retries and the delivery log
Answer with any 2xx status within ten seconds. Anything else counts as a failure, and we try again after 1, 5, 30, 120, 360 and 720 minutes, about 21 hours in all, before marking it failed.
Because of retries, the same event can arrive more than once. Every event has an id: skip ids you have already handled.
Each webhook has a delivery log showing every attempt and its answer. Send again resends a delivery, which is handy after you fix a bug.
Rotating the secret
If a secret may have leaked, rotate it from the webhook's menu. Messages are signed with the new secret straight away, so update your server first.
Webhooks are not a backup
