Skip to main content

List available event types

Retrieve the complete list of event types your subscriptions can listen to, along with human-readable descriptions.
GET https://api.tagpay.ng/v1/merchant/webhook/events

Response

status
boolean
true when the request succeeded.
data
object[]
Array of event type descriptors.
curl --request GET \
  --url "https://api.tagpay.ng/v1/merchant/webhook/events" \
  --header "Authorization: Bearer <token>"
{
  "status": true,
  "message": "Available event types",
  "data": [
    { "type": "transaction.success", "description": "Triggered when a transaction completes successfully" },
    { "type": "transaction.failed", "description": "Triggered when a transaction fails" },
    { "type": "transaction.pending", "description": "Triggered when a transaction is pending" },
    { "type": "wallet.credit", "description": "Triggered when a wallet is credited" },
    { "type": "wallet.debit", "description": "Triggered when a wallet is debited" },
    { "type": "customer.created", "description": "Triggered when a new customer is created" },
    { "type": "card.linked", "description": "Triggered when a card is linked to a wallet" },
    { "type": "transfer.completed", "description": "Triggered when a transfer completes successfully" },
    { "type": "transfer.failed", "description": "Triggered when a transfer fails" },
    { "type": "*", "description": "Subscribe to ALL webhook events" }
  ]
}

Event type reference

Standardized events

These are the recommended event types for new integrations.
EventDescription
transaction.successA transaction completed successfully.
transaction.failedA transaction failed.
transaction.pendingA transaction is awaiting processing or approval.
wallet.creditA customer or merchant wallet received funds.
wallet.debitA customer or merchant wallet was debited.
customer.createdA new customer account was created under your merchant.
card.linkedA card was linked to a customer wallet.
transfer.completedA fund transfer completed successfully.
transfer.failedA fund transfer failed.
*Wildcard — receives all events.

Legacy events

These event types are preserved for backward compatibility with existing integrations. New integrations should prefer the standardized events above.
EventDescription
nip_debitNIP debit occurred.
card_debitCard debit occurred.
pos_withdrawalPOS withdrawal occurred.
card_debit_reversalCard debit was reversed.
remita_fund_transferRemita fund transfer initiated.
remita_fund_transfer_reversalRemita transfer reversed.
stamp_dutyStamp duty applied.
bank_transferBank transfer initiated.
bank_transfer_reversalBank transfer reversed.
account_fundedMerchant account was funded.
nip_debit_reversalNIP debit reversed.
nip_credit_reversalNIP credit reversed.
wallet_to_wallet_transferWallet-to-wallet transfer completed.
customer_bank_transferCustomer bank transfer initiated.
customer_bank_transfer_reversalCustomer bank transfer reversed.
master_card_qr_paymentMastercard QR payment processed.
customer_account_fundedCustomer account was funded.
customer_wallet_creditedCustomer wallet credited.
batch_bank_transferBatch bank transfer initiated.
customer_wallet_debitedCustomer wallet debited.
customer_account_createdCustomer account created.
customer_wallet_batch_creditedBatch wallet credit completed.
customer_wallet_batch_debittedBatch wallet debit completed.
customer_wallet_batch_debitted_by_customerBatch debit initiated by customer.
max_balance_exceededMaximum wallet balance was exceeded.

Webhook payload structure

All events share the same top-level payload structure:
{
  "event": "transaction.success",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "reference": "TXN-20240401-001",
    "amount": 500000,
    "status": "success",
    "currency": "NGN",
    "createdAt": "2024-04-01T10:30:00.000Z"
  }
}
The data object contains fields specific to the event type. For transaction events, it contains the full transaction record. For wallet events, it contains wallet balance details. For customer events, it contains customer profile fields.

Example payloads

{
  "event": "transaction.success",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "reference": "TXN-20240401-001",
    "type": "DEBIT",
    "status": "success",
    "amount": 500000,
    "fee": 1000,
    "vat": 75,
    "total": 501075,
    "currency": "NGN",
    "category": "BANK_TRANSFER",
    "description": "Transfer to Access Bank",
    "createdAt": "2024-04-01T10:30:00.000Z"
  }
}
{
  "event": "wallet.credit",
  "data": {
    "walletId": "wallet_001",
    "amount": 2000000,
    "type": "credit",
    "reference": "FUND-20240401-001",
    "balanceBefore": 500000,
    "balanceAfter": 2500000,
    "currency": "NGN",
    "creditedAt": "2024-04-01T09:00:00.000Z"
  }
}
{
  "event": "customer.created",
  "data": {
    "id": "cust_abc123",
    "firstName": "Amara",
    "lastName": "Okonkwo",
    "email": "[email protected]",
    "phone": "08012345678",
    "createdAt": "2024-04-01T08:00:00.000Z"
  }
}
{
  "event": "card.linked",
  "data": {
    "cardId": "card_xyz789",
    "cardPan": "****4321",
    "walletId": "wallet_001",
    "accountNumber": "0123456789",
    "linkedAt": "2024-04-01T11:00:00.000Z"
  }
}
{
  "event": "test.webhook",
  "data": {
    "message": "This is a test webhook from TagPay",
    "timestamp": "2024-04-01T10:00:00.000Z",
    "subscriptionId": "sub_e5f6a7b8-c9d0-1234-efab-567890123456",
    "merchantId": "merch_001"
  }
}

Signature verification

Every webhook request includes headers you should use to verify that the payload originated from TagPay and has not been tampered with.

Headers

HeaderAlgorithmDescription
x-glide-signatureHMAC-SHA512Legacy signature. HMAC of the raw JSON payload using your private key.
x-webhook-signatureHMAC-SHA256New signature. HMAC of {timestamp}.{payload} using your private key.
x-webhook-timestampUnix timestamp in seconds when the request was signed.
Your private key is the same key used for API authentication. You can retrieve it from the TagPay dashboard under API Keys.

Verifying with Node.js

Use the x-webhook-signature header for new integrations. It combines the timestamp with the payload to prevent replay attacks.
verify-webhook.js
const crypto = require("crypto");

function verifyWebhookSignature(req, privateKey) {
  const signature = req.headers["x-webhook-signature"];
  const timestamp = req.headers["x-webhook-timestamp"];

  if (!signature || !timestamp) {
    throw new Error("Missing webhook signature headers");
  }

  // Reject requests older than 5 minutes to prevent replay attacks
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    throw new Error("Webhook timestamp is too old");
  }

  // Reconstruct the signed string: "{timestamp}.{rawPayload}"
  const rawBody = JSON.stringify(req.body);
  const dataToSign = `${timestamp}.${rawBody}`;

  const expectedSignature = crypto
    .createHmac("sha256", privateKey)
    .update(dataToSign)
    .digest("hex");

  if (!crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  )) {
    throw new Error("Invalid webhook signature");
  }

  return true;
}

// Express handler example
app.post("/webhooks/tagpay", express.json(), (req, res) => {
  try {
    verifyWebhookSignature(req, process.env.TAGPAY_PRIVATE_KEY);
  } catch (err) {
    return res.status(401).json({ error: err.message });
  }

  const { event, data } = req.body;

  switch (event) {
    case "transaction.success":
      // handle successful transaction
      break;
    case "wallet.credit":
      // handle wallet credit
      break;
    default:
      // log unhandled event type
  }

  // Always respond with 2xx to acknowledge receipt
  res.status(200).json({ received: true });
});

Verifying the legacy signature

If you are on the legacy callback URL system, verify using the x-glide-signature header and SHA-512:
verify-legacy-webhook.js
const crypto = require("crypto");

function verifyLegacySignature(req, privateKey) {
  const signature = req.headers["x-glide-signature"];

  if (!signature) {
    throw new Error("Missing x-glide-signature header");
  }

  const rawBody = JSON.stringify(req.body);

  const expectedSignature = crypto
    .createHmac("sha512", privateKey)
    .update(rawBody)
    .digest("hex");

  if (!crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  )) {
    throw new Error("Invalid webhook signature");
  }

  return true;
}
Always use crypto.timingSafeEqual when comparing signatures. Standard string equality (===) is vulnerable to timing attacks.

Retry policy

TagPay attempts delivery up to 3 times per event before marking the delivery as failed.
AttemptTiming
1Immediately after the event fires.
21 minute after the first failure.
35 minutes after the second failure.
After all attempts are exhausted, you can manually trigger another delivery using the replay endpoint.
Your endpoint must respond with a 2xx HTTP status code within 10 seconds for the delivery to be counted as successful. Responses that time out or return 3xx, 4xx, or 5xx status codes are treated as failures and trigger a retry.