Click Airtime

Webhooks

Receive real-time notifications for transaction status changes using Click Airtime webhooks.

Webhooks are only available in the Version 2 API. The Version 1 API does not support webhooks — use polling via GET /adp/transactions instead.

Webhooks allow your application to receive real-time HTTP callbacks when transaction statuses change. Instead of polling the API for updates, your server is notified automatically.

How Webhooks Work

1. Your app creates a transaction via the API
2. Click Airtime processes the transaction
3. When the status changes, we send an HTTP POST to your webhook URL
4. Your server acknowledges receipt with a 200 response

Setting Up Webhooks

Register a Webhook URL

Configure your webhook endpoint in the Enterprise dashboard under Settings > Webhooks, or via the API:

curl -X POST https://api.clickairtime.com/enterprise/webhooks \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Company-ID: YOUR_COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/clickairtime",
    "events": ["transaction.completed", "transaction.failed"],
    "secret": "whsec_your_signing_secret"
  }'

Supported Events

EventDescription
transaction.completedAirtime was successfully delivered to the recipient
transaction.failedTransaction failed after all retry attempts
transaction.pendingTransaction is awaiting processing
transaction.refundedTransaction was refunded to the wallet
wallet.low_balanceWallet balance dropped below the configured threshold

Webhook Payload

When an event occurs, Click Airtime sends an HTTP POST request to your configured URL with a JSON payload:

{
  "id": "evt_a1b2c3d4e5f6",
  "type": "transaction.completed",
  "timestamp": "2025-01-15T10:30:02.500Z",
  "data": {
    "transactionId": "txn_7g8h9i0j1k2l",
    "status": "completed",
    "recipient": {
      "phoneNumber": "+233541112259",
      "network": "MTN Ghana",
      "country": "Ghana"
    },
    "product": {
      "type": "custom_airtime",
      "localAmount": 5.00,
      "currency": "GHS"
    },
    "sourceAmount": 0.34,
    "sourceCurrency": "USD",
    "idempotencyKey": "txn_abc123_001",
    "completedAt": "2025-01-15T10:30:02.500Z"
  }
}

Verifying Webhook Signatures

Every webhook request includes a signature in the X-Click-Signature header. Always verify this signature to ensure the request originated from Click Airtime.

The signature is an HMAC-SHA256 hash of the request body using your webhook secret.

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express.js example
app.post('/webhooks/clickairtime', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-click-signature'];
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(payload);

  switch (event.type) {
    case 'transaction.completed':
      handleTransactionCompleted(event.data);
      break;
    case 'transaction.failed':
      handleTransactionFailed(event.data);
      break;
  }

  // Always respond with 200 to acknowledge receipt
  res.status(200).send('OK');
});
import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = 'whsec_your_signing_secret'

def verify_webhook_signature(payload, signature, secret):
    expected = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

@app.route('/webhooks/clickairtime', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Click-Signature', '')
    payload = request.get_data(as_text=True)

    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        return 'Invalid signature', 401

    event = request.get_json()

    if event['type'] == 'transaction.completed':
        handle_transaction_completed(event['data'])
    elif event['type'] == 'transaction.failed':
        handle_transaction_failed(event['data'])

    # Always respond with 200 to acknowledge receipt
    return 'OK', 200
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_CLICK_SIGNATURE'] ?? '';
$secret = getenv('WEBHOOK_SECRET');

$expected = hash_hmac('sha256', $payload, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    echo 'Invalid signature';
    exit;
}

$event = json_decode($payload, true);

switch ($event['type']) {
    case 'transaction.completed':
        handleTransactionCompleted($event['data']);
        break;
    case 'transaction.failed':
        handleTransactionFailed($event['data']);
        break;
}

// Always respond with 200 to acknowledge receipt
http_response_code(200);
echo 'OK';

Retry Policy

If your webhook endpoint does not respond with a 2xx status code within 10 seconds, Click Airtime will retry the delivery:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry24 hours

After 5 failed attempts, the webhook delivery is marked as failed. You can view failed deliveries and manually retry them from the Enterprise dashboard under Settings > Webhooks > Delivery Log.

Best Practices

  1. Respond quickly -- Return a 200 response as soon as you receive the webhook. Process the event asynchronously using a job queue.

  2. Verify signatures -- Always validate the X-Click-Signature header to prevent spoofing.

  3. Handle duplicates -- Webhooks may be delivered more than once. Use the event id field to deduplicate.

  4. Use HTTPS -- Your webhook endpoint must use HTTPS in production. HTTP endpoints are only accepted in sandbox mode.

  5. Log all events -- Store the raw webhook payload for debugging and audit trails.

  6. Monitor delivery failures -- Set up alerts for consecutive webhook delivery failures in your dashboard.

Testing Webhooks: Use the sandbox environment to test webhook delivery without affecting production data. You can also trigger test events from the Enterprise dashboard under Settings > Webhooks > Send Test Event.