Skip to main content
Webhooks let your server receive automatic HTTP POST notifications when events happen in your TagPay account — such as a wallet credit, a transfer completing, or a new customer being created. Instead of polling the API for state changes, TagPay pushes event data directly to your endpoint.

Webhook modes

TagPay supports two delivery modes. Choose the one that fits your integration:

Legacy mode

All events are delivered to a single callbackURL configured on your merchant account. Simple to set up, but offers no per-event filtering or delivery tracking in the API.

Subscription mode

Subscribe to specific event types with dedicated endpoints. Supports delivery history, replays, and per-subscription testing. Recommended for production integrations.
You can also set the mode to BOTH to deliver events to your callback URL and all active subscriptions simultaneously.

Set your webhook mode

Use PUT /merchant/webhook/settings to switch between modes:
cURL
curl -X PUT https://api.tagpay.ng/v1/merchant/webhook/settings \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookMode": "SUBSCRIPTIONS"
  }'
Valid values for webhookMode are LEGACY, SUBSCRIPTIONS, or BOTH.

Creating a subscription

Use POST /merchant/webhook/subscribe to register a URL that receives events for specific event types.
cURL
curl -X POST https://api.tagpay.ng/v1/merchant/webhook/subscribe \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/tagpay",
    "events": ["transaction.success", "wallet.credit", "transfer.completed"],
    "description": "Production event handler"
  }'
Response
{
  "success": true,
  "data": {
    "id": "sub_abc123",
    "url": "https://your-server.com/webhooks/tagpay",
    "events": ["transaction.success", "wallet.credit", "transfer.completed"],
    "description": "Production event handler",
    "active": true,
    "createdAt": "2026-04-08T10:00:00.000Z"
  }
}
FieldTypeRequiredDescription
urlstringYesThe HTTPS endpoint that receives event POSTs
eventsarrayYesList of event types to subscribe to. Use ["*"] for all events
descriptionstringNoA label to identify this subscription
Webhook URLs must use HTTPS in production. HTTP endpoints are blocked.

Managing subscriptions

curl -X GET https://api.tagpay.ng/v1/merchant/webhook/subscriptions \
  -H "Authorization: Bearer <your-token>"

Available event types

EventDescription
transaction.successA transaction completed successfully
transaction.failedA transaction failed
transaction.pendingA transaction is pending processing
wallet.creditA customer wallet was credited
wallet.debitA customer wallet was debited
customer.createdA new customer wallet was created
card.linkedA card was linked to a customer wallet
transfer.completedA bank or wallet transfer completed successfully
transfer.failedA bank or wallet transfer failed
*Subscribe to all event types
To retrieve the authoritative list of supported events from the API:
cURL
curl -X GET https://api.tagpay.ng/v1/merchant/webhook/events \
  -H "Authorization: Bearer <your-token>"

Webhook payload structure

All webhook deliveries POST a JSON body in this format:
{
  "event": "transaction.success",
  "data": {
    "transactionId": "txn_001",
    "reference": "TXN-2026-001",
    "type": "credit",
    "amount": 50000,
    "fee": 0,
    "currency": "NGN",
    "status": "success",
    "description": "Wallet credit",
    "timestamp": "2026-04-08T10:00:00.000Z",
    "metadata": {
      "merchantId": "mer_xyz",
      "merchantName": "Acme Payments"
    }
  }
}
For wallet events, the data object includes balance information:
{
  "event": "wallet.credit",
  "data": {
    "walletId": "wal_001",
    "accountNumber": "1234567890",
    "previousBalance": 10000,
    "newBalance": 60000,
    "amount": 50000,
    "type": "credit",
    "reference": "REF-2026-001",
    "timestamp": "2026-04-08T10:00:00.000Z"
  }
}

Verifying webhook signatures

TagPay signs every webhook delivery so you can confirm it originated from TagPay and has not been tampered with. Two signature headers are sent with each request:
HeaderAlgorithmDescription
x-glide-signatureHMAC-SHA512Legacy signature — hash of the raw JSON payload
x-webhook-signatureHMAC-SHA256Timestamp-prefixed signature for replay protection
x-webhook-timestampUnix timestamp in seconds used in the SHA-256 signature
Your merchant private key (available from GET /merchant/my-access-keys) is used as the signing secret.

Verify with Node.js

const crypto = require("crypto");

function verifyWebhookSignature(req, merchantPrivateKey) {
  const payload = JSON.stringify(req.body);

  // Verify the SHA-512 legacy signature
  const expectedLegacySignature = crypto
    .createHmac("sha512", merchantPrivateKey)
    .update(payload)
    .digest("hex");

  const legacySignature = req.headers["x-glide-signature"];
  const legacyValid = crypto.timingSafeEqual(
    Buffer.from(expectedLegacySignature, "hex"),
    Buffer.from(legacySignature, "hex")
  );

  // Verify the SHA-256 timestamp signature
  const timestamp = req.headers["x-webhook-timestamp"];
  const timestampedPayload = `${timestamp}.${payload}`;

  const expectedTimestampSignature = crypto
    .createHmac("sha256", merchantPrivateKey)
    .update(timestampedPayload)
    .digest("hex");

  const timestampSignature = req.headers["x-webhook-signature"];
  const timestampValid = crypto.timingSafeEqual(
    Buffer.from(expectedTimestampSignature, "hex"),
    Buffer.from(timestampSignature, "hex")
  );

  // Reject stale payloads (older than 5 minutes)
  const now = Math.floor(Date.now() / 1000);
  const isRecent = Math.abs(now - parseInt(timestamp, 10)) < 300;

  return (legacyValid || timestampValid) && isRecent;
}

// Express route example
app.post("/webhooks/tagpay", express.json(), (req, res) => {
  const isValid = verifyWebhookSignature(req, process.env.TAGPAY_PRIVATE_KEY);

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const { event, data } = req.body;

  switch (event) {
    case "transaction.success":
      // Handle successful transaction
      break;
    case "wallet.credit":
      // Handle wallet credit
      break;
    case "transfer.completed":
      // Handle completed transfer
      break;
    default:
      console.log("Unhandled event:", event);
  }

  // Always return 2xx to acknowledge receipt
  res.status(200).json({ received: true });
});
Always return a 200 status code promptly to acknowledge receipt. TagPay considers any non-2xx response a delivery failure and will retry.

Delivery history

View the delivery status of all webhook events for your account:
cURL
curl -X GET "https://api.tagpay.ng/v1/merchant/webhook/deliveries?status=failed&page=1" \
  -H "Authorization: Bearer <your-token>"
Query paramDescription
statusFilter by pending, success, failed, or retrying
eventTypeFilter by event type (e.g., transaction.success)
subscriptionIdFilter by a specific subscription
startDateISO date string for the start of the range
endDateISO date string for the end of the range
pagePage number (default: 1)
To inspect a specific delivery:
cURL
curl -X GET https://api.tagpay.ng/v1/merchant/webhook/delivery/del_001 \
  -H "Authorization: Bearer <your-token>"

Replaying failed deliveries

If a delivery failed — for example, because your server was temporarily down — you can replay it:
cURL
curl -X POST https://api.tagpay.ng/v1/merchant/webhook/replay/del_001 \
  -H "Authorization: Bearer <your-token>"
This creates a new delivery record with a fresh payload signing and sends the original event data to the subscription’s URL.

Testing a webhook endpoint

Before going live, verify your endpoint handles events correctly using POST /merchant/webhook/test/:id, where :id is the subscription ID.
cURL
curl -X POST https://api.tagpay.ng/v1/merchant/webhook/test/sub_abc123 \
  -H "Authorization: Bearer <your-token>"
TagPay sends a test.webhook event to your endpoint and returns the HTTP status code and response time. The test payload looks like:
{
  "event": "test.webhook",
  "data": {
    "message": "This is a test webhook from TagPay",
    "timestamp": "2026-04-08T10:00:00.000Z",
    "subscriptionId": "sub_abc123",
    "merchantId": "mer_xyz"
  }
}

Retry behavior

When a delivery fails (non-2xx response or connection timeout), TagPay retries automatically with exponential backoff:
AttemptDelay
1 (initial)Immediate
21 minute
35 minutes
After 3 failed attempts, the delivery is marked as failed and no further retries are made. Use the replay endpoint to manually retry failed deliveries.
TagPay enforces a rate limit of 100 webhook deliveries per minute per subscription. Deliveries that exceed this limit are not sent and are recorded as rate-limited failures.