Blog
Guides 8 min read

Webhook Best Practices for Payment Integrations

Webhooks are the backbone of payment integrations. Get them wrong and you will miss payments, double-charge customers, or create security vulnerabilities. Here are the patterns that work.

Why webhooks matter for payments

When a customer pays via bank transfer or mobile money, the payment is asynchronous. Your server initiates the transaction, but the result arrives minutes (sometimes hours) later via a webhook. If your webhook handler is unreliable, you will miss successful payments, leaving customers frustrated and your revenue unrecorded.

Crezaro delivers webhooks for every significant event in the payment lifecycle: payment success, payment failure, refund processed, dispute opened, subscription renewed, and more. Your job is to handle them correctly.

Always verify the signature

Every webhook Crezaro sends includes an x-crezaro-signature header. This is an HMAC-SHA512 hash of the request body, computed using your secret key. Always verify this signature before processing the webhook.

// Node.js example
const crypto = require('crypto');

function verifyWebhook(body, signature, secretKey) {
  const hash = crypto
    .createHmac('sha512', secretKey)
    .update(body)
    .digest('hex');

  return hash === signature;
}

// In your Express handler:
app.post('/webhooks/crezaro', express.raw({ type: '*/*' }), (req, res) => {
  const isValid = verifyWebhook(
    req.body,
    req.headers['x-crezaro-signature'],
    process.env.CREZARO_SECRET_KEY
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  // Process the event...
  res.status(200).send('OK');
});

Important: use the raw request body for signature verification, not a parsed-and-re-serialized version. JSON serialization is not deterministic, and re-serializing can change the byte sequence.

Make your handler idempotent

Crezaro guarantees at-least-once delivery. This means the same webhook event may be delivered more than once, for example if your server responded with a timeout or a 5xx error. Your handler must be able to process the same event multiple times without side effects.

The simplest pattern is to track processed event IDs:

// PHP / Laravel example
public function handleWebhook(Request $request)
{
    $event = $request->json();
    $eventId = $event['id'];

    // Check if we have already processed this event
    if (ProcessedWebhook::where('event_id', $eventId)->exists()) {
        return response('Already processed', 200);
    }

    // Process the event
    $this->processPaymentEvent($event);

    // Record that we processed it
    ProcessedWebhook::create(['event_id' => $eventId]);

    return response('OK', 200);
}

Respond quickly, process asynchronously

Crezaro expects a response within 30 seconds. If your handler takes longer, the delivery is marked as failed and will be retried. Do not perform heavy processing in the webhook handler itself. Instead, acknowledge receipt immediately and process the event asynchronously.

// Good: acknowledge immediately, process in background
app.post('/webhooks/crezaro', (req, res) => {
  // Verify signature...

  // Queue for async processing
  eventQueue.add(req.body);

  // Respond immediately
  res.status(200).send('OK');
});

Handle retries gracefully

Crezaro retries failed webhook deliveries with exponential backoff:

  • 1st retry: 5 minutes after initial failure
  • 2nd retry: 30 minutes
  • 3rd retry: 2 hours
  • 4th retry: 8 hours
  • 5th retry: 24 hours

After five failed attempts, the webhook is marked as permanently failed and appears in your dashboard under Developers > Webhooks > Failed. You can manually retry from there.

To minimize retries, ensure your endpoint:

  • Returns a 200 status code on success (any 2xx is accepted)
  • Does not redirect (3xx responses are treated as failures)
  • Is accessible from the internet (not behind a VPN or firewall that blocks our IPs)
  • Responds within 30 seconds

Use webhook events, not callbacks, as your source of truth

When a customer completes payment, they are redirected to your callback URL. It is tempting to use this redirect as confirmation that the payment succeeded. Do not do this. The callback URL redirect can be spoofed, and the customer might close their browser before the redirect happens.

The correct flow is:

  1. Customer completes payment and is redirected to your callback URL
  2. Your callback page shows a "Processing..." state
  3. Your server receives the webhook confirming the payment status
  4. Your server updates the order status
  5. The callback page polls your API and updates to show "Payment confirmed"

Testing webhooks locally

During development, your local machine is not accessible from the internet. Use a tunneling service like ngrok to expose your local webhook endpoint:

# Start your local server
npm start  # Runs on port 3000

# In another terminal, expose it
ngrok http 3000

# Use the ngrok URL as your webhook URL in the Crezaro dashboard
# https://abc123.ngrok.io/webhooks/crezaro

Alternatively, use the Crezaro CLI to forward webhook events directly to your local server without a tunneling service. See our CLI documentation for setup instructions.

Checklist

Before going live, confirm that your webhook implementation covers these points:

  • Signature verification on every request
  • Idempotent processing (duplicate events are safe)
  • Fast acknowledgment (process heavy work asynchronously)
  • Error handling that does not leak sensitive information
  • Logging of all received events for debugging
  • Monitoring and alerting on webhook processing failures

Questions? Our developer support team is available at developers@crezaro.com or through the chat widget in your dashboard.

S

Written by

Samuel Olaoye

Founder & CEO at Crezaro

Share X LinkedIn

Building the payment infrastructure Africa deserves. Passionate about fintech, developer experience, and financial inclusion across the continent.

Topics API Security Developers
Newsletter

The Crezaro
Newsletter

Enjoyed this post? Get more like it every Tuesday — payments, engineering, and infrastructure for builders.

A
B
C
+2,000 readers
Builders, operators & engineers
  • Signal, not noise
    Curated insights worth your time
  • Engineering depth
    Decisions explained, not just announced
  • Every Tuesday
    Consistent — unsubscribe in one click

Every Tuesday · No spam · Unsubscribe anytime