Skip to content

Standard Webhooks libraries

Our webhooks follow Standard Webhooks, an open specification with official libraries. Verify with them; do not write your own signature check.

Language Package Install
Node standardwebhooks npm install standardwebhooks
PHP standard-webhooks/standard-webhooks composer require standard-webhooks/standard-webhooks
Python standardwebhooks pip install standardwebhooks
Go libraries/go go get github.com/standard-webhooks/standard-webhooks/libraries

The repository also has libraries for Java, C#, Ruby, Rust and Elixir.

Pass the library your endpoint’s secret as it is, whsec_…: the libraries read the prefix. Give it the raw body and the request’s headers; it checks the signature and refuses a timestamp more than 5 minutes off.

examples/node/webhooks/receive.mjs
// Receive webhooks: verify each one with the Standard Webhooks library, act on it once.
import { createServer } from "node:http";
import { Webhook } from "standardwebhooks";
const webhook = new Webhook(process.env.WEBHOOK_SECRET); // whsec_…, answered once by PUT /v1/webhook-endpoint
const handled = new Set(); // webhook-id of the events acted on; keep this in your database
const versions = new Map(); // the highest version applied, per payment, refund or payout
createServer((req, res) => {
const chunks = [];
req.on("data", (chunk) => chunks.push(chunk));
req.on("end", () => {
let event;
try {
// The exact bytes received, before any JSON parsing; the timestamp must be within 5 minutes.
event = webhook.verify(Buffer.concat(chunks).toString("utf8"), req.headers);
} catch {
res.writeHead(400).end();
return;
}
if (!handled.has(req.headers["webhook-id"])) {
handled.add(req.headers["webhook-id"]);
apply(event);
}
res.writeHead(204).end(); // answer 2xx within 15 seconds; do slow work after answering
});
}).listen(Number(process.env.PORT ?? 8080));
function apply(event) {
const [subject, version] = {
"payment.status_changed": [event.payment_id, event.payment_version],
"refund.status_changed": [event.refund_id, event.refund_version],
"payout.status_changed": [event.payout_id, event.payout_version],
}[event.type] ?? [event.event_id, 0];
// Events may come late or twice: an older version than the one applied changes nothing.
if ((versions.get(subject) ?? 0) > version) return;
versions.set(subject, version);
console.log(`verified ${event.type} ${subject} v${version} ${event.status ?? ""}`);
}

More: receiving webhooks.