Webhooks
Payofy tells your app when something happens, such as a payment succeeding, by sending an HTTP POST to a URL you choose.
Preview
Webhook delivery is being rolled out. This page describes the format Payofy uses, so you can build your endpoint now.
Events
| Event | When it happens |
|---|---|
merchant.created | A merchant was registered |
merchant.approved | A merchant can now take payments |
payment.succeeded | A payment went through |
payment.failed | A payment was declined or failed |
refund.processed | A refund was completed |
invoice.paid | An invoice was paid |
invoice.past_due | An invoice wasn't paid on time |
dispute.opened | A customer disputed a payment |
payout.settled | Money reached the merchant's bank account |
What Payofy sends
{
"id": "3f2b8c1e-4a5d-4e6f-9a7b-1c2d3e4f5a6b",
"event": "payment.succeeded",
"tenantId": "…",
"data": { "paymentId": "…", "amountCents": 1250, "currency": "USD" },
"timestamp": "2026-01-15T10:00:00.000Z"
}
Verify every webhook
Anyone can send a request to your URL, so check that each webhook really came from Payofy. Every webhook has an x-payofy-signature header: an HMAC-SHA256 of the raw request body, signed with your webhook secret and hex-encoded.
With the Node SDK:
import express from "express";
import { Payofy } from "@payofy/node";
const payofy = new Payofy({ apiKey: process.env.PAYOFY_SECRET_KEY });
const app = express();
// Use the raw body. Parsing JSON first changes the bytes and breaks the signature.
app.post("/webhooks/payofy", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
event = payofy.webhooks.parseEvent(
req.body.toString("utf8"),
req.header("x-payofy-signature") ?? "",
process.env.PAYOFY_WEBHOOK_SECRET,
);
} catch {
return res.status(400).send("Invalid signature");
}
if (event.event === "payment.succeeded") {
// Mark the order as paid in your database.
}
res.sendStatus(200);
});
Good practice
- Reply fast. Return
200as soon as you've verified and stored the event, and do slow work afterwards. - Expect duplicates. The same event can arrive more than once. Use its
idto ignore repeats. - Keep the secret on your server, like your API key.