- WooCommerce
- Magento
- Shopify Soon
Real-time notifications
Stay informed about payment events with real-time webhooks. Automate your order processing, and more.
Overview
Webhooks allow you to receive real-time notifications about events that occur in your Reservepay account. Instead of polling our API to check for updates, your server can listen for incoming requests from Reservepay.
Setting up webhooks
To start receiving webhook notifications, connect a webhook endpoint to your account. You can manage webhooks through the API or from your merchant dashboard under Developer Settings.
You can connect several endpoints to one account. Each endpoint has its own destination_url, events to subscribe to, signature_type, and api_version. When an event happens, every active endpoint subscribed to it gets its own copy.
Currently supported events
| Event | Description |
|---|---|
payment_authorized |
Triggered when a payment status transitions to AUTHORIZED. |
payment_completed |
Triggered when a payment status transitions to SUCCESSFUL or FAILED. |
payment_expired |
Triggered when a payment status transitions to EXPIRED. |
payment_voided |
Triggered when a payment status transitions to VOIDED. |
payment_reversed |
Triggered when a payment status transitions to REVERSED. |
payout_completed |
Triggered when a payout status transitions to SUCCESSFUL or FAILED. |
topup_settled |
Triggered when a topup status transitions to SUCCESSFUL. |
payment_completed and payout_completed fire for both success and failure. Always read the status in the payload before you fulfill an order or mark a payout as paid. For what each status means, see Payment lifecycle.
Track refunds and disputes by polling, since they don't have events. Poll refunds with find-refund (see Refunds, voids, and reversals) and disputes with list-disputes (see Disputes).
Handling webhook requests
When an event occurs, Reservepay sends a POST request to your destination URL with a JSON payload.
Payload
Payment and payout deliveries include the following fields. See the Webhook reference for each event's complete payload:
event_id: A unique ID for the event, starting withevt_. It stays the same across retries and across endpoints, so use it to spot duplicates.status: The payment or payout status when the event happened.delivered_at: When this delivery attempt was sent, as a Unix timestamp. It changes on every retry, so two deliveries of the same event have different bodies and signatures. Spot duplicates withevent_id, not with a hash of the body.external_id: Your own reference, if you set one.
Verifying signatures
Every delivery is signed, so you can confirm it came from Reservepay. Check the signature before you trust the event. You choose the signature type when you connect the endpoint, and you can find the endpoint's verification_key in the dashboard.
Check the signature against the body exactly as you received it, before you parse it. Parsing and re-encoding the JSON changes the bytes and breaks the signature. Reject a request whose signature is missing or doesn't match.
| Signature type | Headers |
|---|---|
HMAC_SHA256 |
Reservepay-Signature |
ED25519 |
Reservepay-Signature |
SHA256 |
X-Webhook-Signature |
The header value is prefixed with the scheme name, in the form <scheme>=<hex digest> (for example hmac_sha256=<hex>).
HMAC (SHA256)
Compute an HMAC-SHA256 digest of the raw request body using your verification key (Base64-decoded) and compare it to the hex value after hmac_sha256= in the Reservepay-Signature header.
This Node.js function does the check:
import { createHmac, timingSafeEqual } from "node:crypto"
function verifyReservepaySignature(rawBody, header, verificationKey) {
const [scheme, signature] = (header ?? "").split("=")
if (scheme !== "hmac_sha256" || !signature) return false
const expected = createHmac("sha256", Buffer.from(verificationKey, "base64"))
.update(rawBody)
.digest("hex")
const received = Buffer.from(signature)
const computed = Buffer.from(expected)
return received.length === computed.length && timingSafeEqual(received, computed)
}
Pass it the body exactly as it arrived. In Express, read the body with express.raw() rather than express.json(), then parse the JSON only after the signature checks out:
app.post("/webhooks/reservepay", express.raw({ type: "application/json" }), async (req, res) => {
const valid = verifyReservepaySignature(
req.body,
req.get("Reservepay-Signature"),
process.env.RESERVEPAY_WEBHOOK_KEY,
)
if (!valid) return res.sendStatus(401)
const event = JSON.parse(req.body.toString("utf8"))
await saveEventOnce(event) // Your code: store the event, skipping any event_id you already have.
res.sendStatus(200)
})
Ed25519
Verify the hex-decoded signature after ed25519= in the Reservepay-Signature header against the raw request body using your verification key (Base64-decoded Ed25519 public key). Use a libsodium-compatible library for verification.
SHA256
For this legacy scheme, append the verification_key string exactly as returned by the API to the raw JSON body, with no separator. Compute the SHA256 hex digest of those bytes and compare it to the value after sha256= in the X-Webhook-Signature header. Do not Base64-decode the key for this scheme.
Choose HMAC_SHA256 or ED25519 for new endpoints.
Responding to webhooks
Return any 2xx status within 10 seconds. Reservepay ignores the response body. To stay within the time limit:
- Check the signature.
- Save the event, skipping it if you already have its
event_id. - Return
200 OK. - Do the actual work, such as fulfilling the order, in a background job.
If a duplicate arrives for an event you already saved, return 200 OK again so Reservepay stops retrying.
Retries and ordering
If your endpoint times out, can't be reached, or returns anything other than 2xx, Reservepay retries with increasing delays, up to 20 attempts in total. Because of retries, the same event can arrive more than once, and events can arrive out of order.
Don't use arrival order or delivered_at to decide which status is newer. When you need the current status, call find-payment or find-payout.
Checking delivery history
To see whether Reservepay reached your endpoint, call list-payment-activities with a payment_id, or list-payout-activities with a payout_id. Each event in the response has an event_deliveries array. Each entry covers one endpoint and one outcome:
url: The endpoint Reservepay called.status_code: The HTTP status your endpoint returned, or0if it didn't respond.failure_code: Why the delivery failed, ornullif it succeeded.occurrences: How many attempts ended with this outcome.payload: The body that was sent.
An event can have both failed and successful entries for the same endpoint when a retry succeeded. The event was delivered if any entry for that endpoint has failure_code set to null.
Recovering missed events
After 20 failed attempts, Reservepay stops retrying the event, and deliveries can't be resent. If your endpoint was down for longer than the retry window, compare your records with the API instead, as described in Recovering missed updates.
Rotating the signing key
An endpoint keeps the same verification_key for as long as it exists. To move to a new HMAC_SHA256 key:
- Connect a second endpoint with
merchants/connect-webhook, using the same destination URL, and store itsverification_key. - While both endpoints are active, try each key and accept the request if either one matches. Every event is delivered to both endpoints with the same
event_id, so your deduplication handles the overlap. - When deliveries to the new endpoint are succeeding, remove the old one through the dashboard.
ED25519 endpoints all verify with Reservepay's public key, so connecting a new endpoint doesn't give you a new key.
Dive deeper into these advanced integration topics to unlock Reservepay's full potential with our in-depth guides.
Issue refunds, void transactions before they settle, and reverse authorizations when needed.
Read the guideSeparate authorization from capture to hold funds without charging. Perfect for pre-orders or reservations
Read the guideBuild your own POS, or work with our app by sending commands or using deep links.
Read the guideBuild profiles by attaching customer data. Gain insights, reduce fraud risk, and create experiences that drive loyalty.
Read the guideSend payouts to your primary bank account or to your contacts via bank account, mobile number or national ID.
Read the guideStay informed about payment events with real-time webhooks. Automate your order processing, and more...
Read the guideFilter payments and payouts, export CSVs, read balance holds, and reconcile daily statements.
Read the guideLook up what each Reservepay failure code means when a payment is declined.
Read the guideNo account yet?
Start integrating all these amazing features into your app or website by creating your own merchant account today. It's free to sign up and only takes a few minutes to get started.
