Skip to main content

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​

EventWhen it happens
merchant.createdA merchant was registered
merchant.approvedA merchant can now take payments
payment.succeededA payment went through
payment.failedA payment was declined or failed
refund.processedA refund was completed
invoice.paidAn invoice was paid
invoice.past_dueAn invoice wasn't paid on time
dispute.openedA customer disputed a payment
payout.settledMoney 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 200 as 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 id to ignore repeats.
  • Keep the secret on your server, like your API key.