Skip to main content
Webhooks let Hyparrow notify your server when a payment completes asynchronously — such as a virtual account deposit, USSD payment, or OPay collection. Instead of polling for status, configure a webhook URL and Hyparrow will POST the event to you.

How it works

1

Configure your webhook URL

Set a public HTTPS endpoint on your server where Hyparrow can POST events.
2

Generate a webhook secret

Generate a signing secret. Hyparrow uses it to sign every payload with HMAC-SHA512.
3

Verify incoming requests

On your server, verify the X-Hyparrow-Signature header before processing the event.
4

Respond with 200

Return HTTP 200 immediately. Hyparrow does not resend if your server responds slowly.

Configure your webhook URL

curl -X PUT https://api.hyparrow.com/api/v1/webhook-settings \
  -H "X-API-Key: pk_abc123..." \
  -H "X-API-Secret: sk_xyz789..." \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUrl": "https://yourapp.com/webhooks/hyparrow",
    "allowedIps": []
  }'
webhookUrl
string
required
The HTTPS URL on your server that will receive webhook POSTs.
allowedIps
array
Optional list of IP addresses to restrict webhook delivery from. Leave empty to allow all.

Generate a webhook secret

curl -X POST https://api.hyparrow.com/api/v1/webhook-settings/generate-secret \
  -H "X-API-Key: pk_abc123..." \
  -H "X-API-Secret: sk_xyz789..."
{
  "success": true,
  "message": "Webhook secret generated successfully. Store this securely - it will not be shown again.",
  "data": {
    "secret": "a3f8c2d1e9b04567..."
  }
}
The secret is shown once. Store it securely as an environment variable. If lost, generate a new one — the old one becomes invalid.

Verify webhook signatures

Every webhook Hyparrow sends includes an X-Hyparrow-Signature header containing an HMAC-SHA512 hex digest of the raw request body signed with your webhook secret.
const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha512', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// Express example
app.post('/webhooks/hyparrow', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.headers['x-hyparrow-signature'];
  if (!verifyWebhook(req.body, sig, process.env.HYPARROW_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  // handle event...
  res.sendStatus(200);
});
Always read the raw request body before parsing JSON. Parsing first will alter the bytes and cause signature verification to fail.

Webhook events

customer.transaction.completed

Fired when a payment is received on a customer’s virtual account.
{
  "event": "customer.transaction.completed",
  "timestamp": 1712227200,
  "data": {
    "transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "customerId": "a1b2c3d4-...",
    "customer": {
      "id": "a1b2c3d4-...",
      "firstName": "Amina",
      "lastName": "Okafor",
      "email": "amina@example.com",
      "phoneNumber": "08012345678"
    },
    "amount": 500000,
    "currency": "NGN",
    "reference": "MRC2319123456789",
    "description": "Customer deposit from John Doe via Amina Okafor",
    "status": "completed",
    "provider": "interswitch",
    "channel": "NIP",
    "senderName": "John Doe",
    "responseCode": "00",
    "responseMessage": "Approved",
    "completedAt": "2026-04-04T10:00:00Z",
    "metadata": {}
  }
}
amount is in kobo. Divide by 100 to get the naira value (e.g., 500000 = ₦5,000).

Get current settings

curl https://api.hyparrow.com/api/v1/webhook-settings \
  -H "X-API-Key: pk_abc123..." \
  -H "X-API-Secret: sk_xyz789..."
{
  "success": true,
  "data": {
    "webhookUrl": "https://yourapp.com/webhooks/hyparrow",
    "allowedIps": [],
    "hasSecret": true,
    "secretMasked": "a3f8c2d1...567fab90"
  }
}

Send a test webhook

Verify your endpoint is reachable before going live:
curl -X POST https://api.hyparrow.com/api/v1/webhook-settings/test \
  -H "X-API-Key: pk_abc123..." \
  -H "X-API-Secret: sk_xyz789..."

Best practices

  • Return 200 as fast as possible. Handle processing asynchronously in a background job.
  • Check for duplicate events using data.transactionId — the same event can be delivered more than once.
  • Always verify the signature before acting on a webhook payload.