Orivanta PayDocs

Webhooks

Get notified the moment a transaction settles, and verify it's really us.

Polling a status endpoint works, but a webhook tells you the moment something changes — a charge succeeds, a transfer fails, a fund collection expires. Configure a webhookUrl on your application and Orivanta Pay will POST to it as events happen.

Payload shape

Every delivery has the same envelope; only event and the contents of data change:

{
  "event": "payment.success",
  "timestamp": 1735900000,
  "applicationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "data": {
    "reference": "PI-A1B2C3D4E5F6",
    "type": "CHARGE",
    "status": "SUCCESS",
    "amount": 5000,
    "currency": "XAF",
    "feeAmount": 75,
    "netAmount": 4925,
    "paymentMethod": "MTN_MOMO_CM",
    "payerAccount": "670000000",
    "payerName": "Amina Ndongo",
    "payerEmail": null,
    "merchantReference": "CMD-2024-002",
    "description": null,
    "createdAt": "2026-09-05T10:00:00Z",
    "updatedAt": "2026-09-05T10:02:14Z",
    "failureCode": null,
    "failureReason": null
  }
}

timestamp is Unix seconds, not milliseconds. A retried delivery gets a fresh timestamp and a fresh signature — don't treat it as the original event's creation time.

Events

EventFires when
payment.createdA pay-in transaction is created (checkout or charge).
payment.successA pay-in transaction succeeds.
payment.failedA pay-in transaction fails.
payment.cancelledA customer cancels a hosted checkout session.
payment.expiredA hosted checkout session expires unpaid.
payout.createdA transfer is created.
payout.successA transfer succeeds.
payout.failedA transfer fails.
payout.cancelledA transfer is cancelled.
collection.expiredA fund collection link passes its expiry date.
webhook.testSent by the "test webhook" action in your dashboard — not a real transaction.

Pay-in payloads carry payer fields (payerAccount, payerName, payerEmail); pay-out payloads carry beneficiary fields (beneficiaryAccount, beneficiaryName, beneficiaryEmail) instead. Both carry feeAmount/netAmount alongside the gross amount.

Verifying the signature

Every delivery carries two headers:

X-Sharepay-Signature: t=1735900000,v1=5257a869e7bfa601d3f74b1e6c8a3f...
X-Sharepay-Event: payment.success

Header name

The header is literally X-Sharepay-Signature — that's the codename the API is implemented under, not a typo. Match it exactly.

The signature is HMAC-SHA256 over the raw request body bytes (not a re-serialized version of the JSON — parsing then re-stringifying can reorder keys or change whitespace and silently break verification), using the webhook secret shown when you generated it (whsec_...).

import crypto from "node:crypto";

function verifyWebhook(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((part) => part.split("=")),
  );
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");

  if (expected !== parts.v1) {
    throw new Error("Signature mismatch");
  }
}

// in your route handler, using the UNPARSED body:
app.post("/webhooks/orivanta", express.raw({ type: "application/json" }), (req, res) => {
  verifyWebhook(
    req.body.toString("utf8"),
    req.header("X-Sharepay-Signature"),
    process.env.ORIVANTA_WEBHOOK_SECRET,
  );

  const event = JSON.parse(req.body);
  // handle event.event / event.data
  res.sendStatus(200);
});

Unsigned deliveries

If no webhook secret is configured on the application, deliveries are still sent — just without X-Sharepay-Signature. Treat any unsigned request to your webhook endpoint as untrusted, and generate a secret before relying on webhooks for anything that changes state on your side.

Retries

A delivery that doesn't get a 2xx response is retried up to 5 times, with increasing backoff:

AttemptRetried after
11 minute
25 minutes
330 minutes
42 hours
5— gives up, delivery stays FAILED

Because retries can arrive well after the original event, always key your handling off the transaction reference inside data, not off delivery order or timestamp. Handle repeats of the same event idempotently — the same reference reaching SUCCESS twice should be a no-op on your side, not a double-fulfillment.

On this page