Orqpay

Webhooks

Receive signed callbacks for invoice status changes and verify them safely.

Orqpay sends an HTTP POST to your webhook URL when an invoice changes state. Configure one endpoint (and secret) per environment — test and live are separate.

Configure

Set the webhook URL and signing secret in the merchant dashboard (Webhooks).
Merchant API keys cannot configure webhooks — use the dashboard.

EnvironmentURL rules
Testhttp or https (localhost / tunnels allowed)
LivePublic https only

Secret is required on first setup (16–255 characters). Leave it blank on later updates to keep the existing secret.

Events

EventMeaningWhat you should do
invoice.createdInvoice createdOptional logging
invoice.payment_detectedPayment seen, not confirmed yetWait
invoice.underpaidPartial paymentWait for top-up
invoice.paidPaid on timeFulfill / ship
invoice.late_paidPaid after checkout expiredFunds received — do not auto-fulfill
invoice.overpaidAmount above requiredContact support if you need help
invoice.expiredNo payment in timeClose the order
invoice.expired_underpaidTop-up window ended still shortClose / contact customer
invoice.settledSettlement finishedAccounting confirmation
invoice.payment_invalidatedA previous payment was reversed on-chainReconcile; do not fulfill on detection alone

Fulfillment rule: treat only invoice.paid as safe to ship. Use invoice.settled for accounting. Never treat invoice.late_paid as a normal fulfillment signal.

Payload

{
  "id": "<invoiceId>-<sequence>",
  "type": "invoice.paid",
  "createdAt": "2026-07-04T12:00:00Z",
  "environment": "test",
  "data": {
    "invoiceId": "uuid",
    "status": "PAID",
    "merchantId": "uuid",
    "referenceId": "your-order-ref",
    "metadata": {},
    "amount": "10",
    "amountReceived": "10",
    "token": "USDC",
    "sequence": 3
  }
}
  • amount / amountReceived are decimal strings in major units ("10" = 10 USDC/USDT).
  • Process each delivery idempotently using envelope id (retries reuse the same id).

Verify signatures

Header:

X-Coinpay-Signature: t=<unix_seconds>,v1=<hmac_hex>
  1. Parse t and v1
  2. Reject if the timestamp is older than 5 minutes
  3. Compute HMAC-SHA256 of {timestamp}.{rawBody} with your webhook secret
  4. Compare with constant-time equality
  5. Always verify the raw request body (do not re-serialize JSON)

Node.js

import { createHmac, timingSafeEqual } from "node:crypto";

function verifyOrqpaySignature(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.trim().split("=")),
  );
  const timestamp = parts.t;
  const signature = parts.v1;
  if (!timestamp || !signature) return false;

  const ageSec = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(ageSec) || ageSec > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  try {
    return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
  } catch {
    return false;
  }
}

Respond with 2xx quickly after verifying. Do heavy work asynchronously on your side.

Delivery

  • Orqpay retries failed deliveries with backoff
  • Inspect and resend from the merchant dashboard (Webhooks)
  • Prefer webhooks over polling; see Invoice lifecycle

Test vs live

Test API keys only affect test invoices and the test webhook. Live uses a separate URL and secret after go-live.

See also: Invoice lifecycle · Errors

On this page