Importapay
Getting started

Webhooks

Configure an HTTPS webhook URL from the Merchant Dashboard. Importapay signs each delivery with your merchant webhook secret and sends the signature in x-importapay-signature.

Delivery behaviour

Return any 2xx response to acknowledge a delivery. Timeouts, 429 responses, and all other non-2xx responses are retried with exponential backoff, for up to eight delivery attempts. Retries retain the same eventId.

Store the eventId before processing the event, and make processing idempotent. A webhook must never create a duplicate business effect when a delivery is retried.

Events

EventMeaning
virtual_account.payment_receivedAn exact customer credit has been confirmed for the dynamic account.
virtual_account.settledPayment processing has completed.
virtual_account.expiredNo valid payment was recorded before the account expired.
virtual_account.failedThe payment could not be applied, such as when the amount does not match or it arrives too late.

Headers and signature verification

Each delivery includes these headers:

HeaderPurpose
x-importapay-signatureHex-encoded HMAC-SHA256 of the exact JSON request body, signed with your webhook secret.
x-importapay-event-idThe same stable identifier provided in the body as eventId.

Verify before parsing

Verify the signature against the raw request bytes before parsing or processing JSON.

For example, in Node.js:

import crypto from "node:crypto";

const expectedSignature = crypto
  .createHmac("sha256", process.env.IMPORTAPAY_WEBHOOK_SECRET!)
  .update(rawRequestBody)
  .digest("hex");

const receivedSignature = request.headers["x-importapay-signature"];
const isValid =
  typeof receivedSignature === "string" &&
  receivedSignature.length === expectedSignature.length &&
  crypto.timingSafeEqual(
    Buffer.from(receivedSignature, "hex"),
    Buffer.from(expectedSignature, "hex"),
  );

Payload example

{
  "eventId": "dynamic-account:66c9b4b736d142b900000001:virtual_account.payment_received",
  "event": "virtual_account.payment_received",
  "createdAt": "2026-08-25T12:00:00.000Z",
  "data": {
    "dynamicAccountId": "66c9b4b736d142b900000001",
    "accountNumber": "0123456789",
    "accountName": "Order INV-1001",
    "bankName": "Example Bank",
    "currency": "NGN",
    "expectedAmount": 50000,
    "status": "paid",
    "expiresAt": "2026-08-25T12:30:00.000Z",
    "paidAt": "2026-08-25T12:00:00.000Z",
    "providerTransactionId": "aella_tx_123"
  }
}

The virtual_account.failed payload adds data.failureReason. Dynamic-account webhook payloads do not include blockchain transaction hashes.

On this page