Developers / Webhooks
Webhooks
We notify your server every time an escrow transaction changes state. Every payload is signed with HMAC-SHA256 so you can verify it came from harrenapay.
Events
| Event | Fires when |
|---|---|
| payment.success | Buyer paid in full. Funds are now held in escrow. |
| payment.failed | Payment was abandoned, declined, or underpaid. |
| escrow.released | Buyer (or auto-release) released funds to the seller. |
| escrow.refunded | Funds returned to the buyer after dispute or cancellation. |
| charge.success | Direct (non-escrow) charge succeeded and was settled instantly. |
| charge.failed | Direct charge was declined, abandoned, or underpaid. |
Payload
JSON body. reference is your original order id; amount is in kobo.
POST your endpoint
{
"event": "payment.success",
"reference": "order_4821",
"amount": 2500000,
"currency": "NGN"
}Signature
Header x-harrenapay-signature is the hex-encoded HMAC-SHA256 of the raw request bodyusing your endpoint signing secret (whsec_...). Always verify BEFORE parsing JSON, and compare in constant time.
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyHarrenaSignature(rawBody: string, header: string | null, secret: string) {
if (!header) return false;
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(header, "hex");
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}Python
import hmac, hashlib
def verify_harrena_signature(raw_body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")Express handler
import express from "express";
app.post("/api/harrenapay-webhook",
express.raw({ type: "application/json" }),
(req, res) => {
const sig = req.header("x-harrenapay-signature");
const raw = req.body.toString();
if (!verifyHarrenaSignature(raw, sig, process.env.HARRENAPAY_WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const evt = JSON.parse(raw);
switch (evt.event) {
case "payment.success": /* mark order paid, release goods */ break;
case "payment.failed": /* cancel order */ break;
case "escrow.released": /* funds released to seller */ break;
case "escrow.refunded": /* funds returned to buyer */ break;
}
res.status(200).send("ok");
}
);Delivery and retries
- Respond with 2xx within 10 seconds. Any other status is treated as a failure.
- Failed deliveries retry once automatically on 5xx or network error.
- You can manually replay any past delivery from the in-app Delivery logs tab.
- Duplicates are possible. Use
referenceas your idempotency key.
Register an endpoint
Add your HTTPS callback URL and pick events from the in-app Developers tab. The signing secret is shown immediately, rotate it any time.
Open Developers tabharrenapay is operated by Harrena Africa Synergy LTD. harrenapay is not a bank. Payment processing is provided by Paystack, our CBN-regulated licensed partner.