Get started
Webhooks
Baynoy POSTs every state change to URLs you register at /dashboard/developers/webhooks. Each delivery is HMAC-SHA256 signed so you can prove it came from us and wasn't tampered with.
Delivery shape
POST with Content-Type: application/json:
POST /your-handler HTTP/1.1
Host: yourapp.com
Content-Type: application/json
Baynoy-Signature: t=1748180400000,v1=a3b1c4d8e9f6...
Baynoy-Event-Id: evt_x1y2z3
Baynoy-Event-Type: payment.succeeded
{
"id": "evt_x1y2z3",
"type": "payment.succeeded",
"created": 1748180400,
"data": {
"object": {
"id": "pay_abc",
"amount": 2000,
"currency": "chf",
"status": "succeeded",
"customer": "cus_def"
}
}
}Verifying the signature
Compute HMAC-SHA256 over ${timestamp}.${raw_body}with the endpoint's secret (shown once when you create the endpoint) and compare the hex digest to the v1= portion of the header.
Reject anything older than 5 minutes — that's our replay-defence window.
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyBaynoy(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.trim().split("="))
);
const t = Number(parts.t);
if (Math.abs(Date.now() - t) > 5 * 60 * 1000) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(
Buffer.from(expected, "utf8"),
Buffer.from(parts.v1, "utf8")
);
}Python
import hmac, hashlib, time
def verify_baynoy(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.strip().split("=") for p in header.split(","))
t = int(parts["t"])
if abs(time.time() * 1000 - t) > 5 * 60 * 1000:
return False
expected = hmac.new(
secret.encode(),
f"{t}.{raw_body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, parts["v1"])PHP
function verify_baynoy($raw_body, $header, $secret) {
$parts = [];
foreach (explode(',', $header) as $p) {
list($k, $v) = explode('=', trim($p), 2);
$parts[$k] = $v;
}
$t = (int) $parts['t'];
if (abs((int)(microtime(true) * 1000) - $t) > 5 * 60 * 1000) {
return false;
}
$expected = hash_hmac('sha256', $t . '.' . $raw_body, $secret);
return hash_equals($expected, $parts['v1']);
}Event types
payment.succeededpayment.failedpayment.refundedcustomer.createdcustomer.updatedinvoice.paidinvoice.voidedpayout.paidpayout.failedsubscription.createdsubscription.renewedsubscription.cancelleddispute.openedRetries
Non-2xx responses retry with exponential backoff for up to 72 hours. Respond with any 2xx (including 200 with empty body) to acknowledge. Don't do real work synchronously — push to a queue, ack fast, process later.