Do you Really Handle Webhooks Securely?

When was the last time you stopped to think about how your systems actually talk to each other? Not just that they do, but how well they do it. Most teams assume their integrations are “good enough” until something breaks or, worse, slows down without anyone noticing. In fintech, this kind of silence can be dangerous. E-commerce apps depend on smooth handshakes with payment gateways. Those gateways rely on timely responses from banks. One small hiccup anywhere in that chain and you’re looking at failed transactions, unhappy customers, and revenue loss. This guide explores webhooks, one of the most common methods for enabling system-to-system communication, including when to use them and how they fit into modern application workflows. You'll learn how to configure and build them securely, along with best practices that will save you time and resources. What is a Webhook? A webhook is an automated message sent from one application to another to notify it that an event has occurred, usually with a data payload. It is essentially an HTTP request that delivers data in real time, unlike polling, which repeatedly checks an API for updates. Most systems communicate and share information using either polling or webhooks. Think of it this way: polling is like ordering a meal from your favorite restaurant, but you have to keep asking if it’s ready. Webhooks, on the other hand, are like a restaurant delivering meals to your house whenever they’re cooked. You don’t have to check; they send it automatically every time food is available. Webhooks are often faster than polling and require less overhead. They work similarly to the SMS notifications your bank sends when you complete a transaction. Below are some of their key advantages: Real-time Data: Webhooks push data instantly when an event occurs, so you don’t need to poll an API repeatedly to get the updated data. Increased Efficiency: Webhooks reduce unnecessary API calls, helping lower server load and bandwidth usage since they are event-driven. Cost Savings: By making fewer API calls, webhooks help save on infrastructure costs and avoid rate limits when interfacing with external services. Improved User Experience: Because webhook responses are immediate, the overall user experience improves, as users don’t have to wait for API calls to complete. Flexibility: Webhooks can send data to multiple resources, making them useful in automating workflows across different applications. How do Webhooks Work? Webhooks let your applications, such as e-commerce platforms, communicate in real time by sending HTTP requests when an event occurs. In an e-commerce application, the process typically follows these steps: Customer Initiates Payment: The customer selects a product, proceeds to checkout, chooses a payment method (e.g., card, bank transfer), enters payment details, and clicks “Pay Now.” Payment Processing: The application sends the payment details to the payment gateway, which validates the information, checks for fraud, and requests authorization. Webhook Triggered by Payment Event: Once the payment is confirmed or fails, the payment gateway sends a webhook notification with the transaction details to the application’s backend. Webhook Processing: The backend verifies the request, updates the order status, and notifies the customer. Webhook Acknowledgment: The application server responds with an HTTP 200 OK to confirm receipt of the webhook. While webhooks offer significant advantages and a seemingly straightforward workflow, setting them up can be tricky. They handle real-time data, which may expose sensitive information and create security risks. Let’s explore the main vulnerabilities you need to note when dealing with webhooks. Common Webhook Vulnerabilities Below are key vulnerabilities to be aware of when working with webhooks. Replay Attacks: An attacker captures a webhook request and replays it later to trick the system into triggering unintended actions, such as duplicating transactions or altering order statuses. Data Exposure: Webhook responses may leak sensitive information, such as customer details or payment data, which bad actors can exploit for social engineering attacks. Improper Handling of Retries: Most webhook providers retry failed deliveries. If your system doesn’t properly handle duplicates (idempotency), it may process the same transaction multiple times, leading to double charges or other unintended actions. Lack of Rate Limiting: Attackers can flood the webhook endpoint with requests, causing a denial of service (DoS) or excessive database writes. With the basics and key considerations for handling webhooks covered, let’s dive into how to build a robust webhook service that keeps your system secure, reliable, and easy to maintain. How to Build a Secure Webhook Service To build a secure webhook service, follow the steps below: This implementation uses TypeScript, but t

Apr 28, 2025 - 11:16
 0
Do you Really Handle Webhooks Securely?

When was the last time you stopped to think about how your systems actually talk to each other? Not just that they do, but how well they do it. Most teams assume their integrations are “good enough” until something breaks or, worse, slows down without anyone noticing.

In fintech, this kind of silence can be dangerous. E-commerce apps depend on smooth handshakes with payment gateways. Those gateways rely on timely responses from banks. One small hiccup anywhere in that chain and you’re looking at failed transactions, unhappy customers, and revenue loss.

This guide explores webhooks, one of the most common methods for enabling system-to-system communication, including when to use them and how they fit into modern application workflows. You'll learn how to configure and build them securely, along with best practices that will save you time and resources.

What is a Webhook?

A webhook is an automated message sent from one application to another to notify it that an event has occurred, usually with a data payload. It is essentially an HTTP request that delivers data in real time, unlike polling, which repeatedly checks an API for updates. Most systems communicate and share information using either polling or webhooks.

Think of it this way: polling is like ordering a meal from your favorite restaurant, but you have to keep asking if it’s ready. Webhooks, on the other hand, are like a restaurant delivering meals to your house whenever they’re cooked. You don’t have to check; they send it automatically every time food is available.

Webhooks are often faster than polling and require less overhead. They work similarly to the SMS notifications your bank sends when you complete a transaction. Below are some of their key advantages:

  • Real-time Data: Webhooks push data instantly when an event occurs, so you don’t need to poll an API repeatedly to get the updated data.
  • Increased Efficiency: Webhooks reduce unnecessary API calls, helping lower server load and bandwidth usage since they are event-driven.
  • Cost Savings: By making fewer API calls, webhooks help save on infrastructure costs and avoid rate limits when interfacing with external services.
  • Improved User Experience: Because webhook responses are immediate, the overall user experience improves, as users don’t have to wait for API calls to complete.
  • Flexibility: Webhooks can send data to multiple resources, making them useful in automating workflows across different applications.

How do Webhooks Work?

Webhooks let your applications, such as e-commerce platforms, communicate in real time by sending HTTP requests when an event occurs. In an e-commerce application, the process typically follows these steps:

How webhook works

  • Customer Initiates Payment: The customer selects a product, proceeds to checkout, chooses a payment method (e.g., card, bank transfer), enters payment details, and clicks “Pay Now.”
  • Payment Processing: The application sends the payment details to the payment gateway, which validates the information, checks for fraud, and requests authorization.
  • Webhook Triggered by Payment Event: Once the payment is confirmed or fails, the payment gateway sends a webhook notification with the transaction details to the application’s backend.
  • Webhook Processing: The backend verifies the request, updates the order status, and notifies the customer.
  • Webhook Acknowledgment: The application server responds with an HTTP 200 OK to confirm receipt of the webhook.

While webhooks offer significant advantages and a seemingly straightforward workflow, setting them up can be tricky. They handle real-time data, which may expose sensitive information and create security risks. Let’s explore the main vulnerabilities you need to note when dealing with webhooks.

Common Webhook Vulnerabilities

Below are key vulnerabilities to be aware of when working with webhooks.

  • Replay Attacks: An attacker captures a webhook request and replays it later to trick the system into triggering unintended actions, such as duplicating transactions or altering order statuses.
  • Data Exposure: Webhook responses may leak sensitive information, such as customer details or payment data, which bad actors can exploit for social engineering attacks.
  • Improper Handling of Retries: Most webhook providers retry failed deliveries. If your system doesn’t properly handle duplicates (idempotency), it may process the same transaction multiple times, leading to double charges or other unintended actions.
  • Lack of Rate Limiting: Attackers can flood the webhook endpoint with requests, causing a denial of service (DoS) or excessive database writes.

With the basics and key considerations for handling webhooks covered, let’s dive into how to build a robust webhook service that keeps your system secure, reliable, and easy to maintain.

How to Build a Secure Webhook Service

To build a secure webhook service, follow the steps below:

This implementation uses TypeScript, but the approach applies to any other programming language.

Step 1: Install Dependencies and Set Up Project

To get started, install the required libraries:

npm install express @types/express body-parser @types/body-parser crypto axios winston @types/winston
  • express: Framework for handling webhook routes.
  • body-parser: Middleware to access raw request payloads (critical for signature validation).
  • crypto: Node.js’s built-in library for SHA256 hashing.
  • axios: For cross-verifying transactions with the payment gateway API.
  • winston: For logging events.

Next, create a .env file in your project directory and add a secret value. This secret allows your server and webhook provider to verify incoming requests.

WEBHOOK_SECRET= 

Finally, import the required dependencies and use express to set up the server.

import express from 'express';
import bodyParser from 'body-parser';
import crypto from 'crypto';
import axios from 'axios';
import winston from 'winston';

// Initialize Express and middleware
const app = express();
const port = 3000;

// Environment variables
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;

// Sample event
interface WebhookEvent {
  id: string;
  event: {
    type: string;
    created_at: string;
  };
  data: {
    id: string;
  };
}

The structure of the WebhookEvent depends on the provider and the specific information your application requires.

Step 2: Create Webhook Logics to Validate Signature, Prevent Replay Attack, and Verify Transaction

Most webhook providers include a signature in the request header. Use your webhook secret to validate this signature.

const validateSignature = (req: express.Request): boolean => {
  // The header can also come as `x-signature or x-webhook-signature  
  const signature = req.headers['verif-hash'] as string;
  const rawBody = req.body.toString('utf-8');

  const expectedSignature = crypto
    .createHash('sha256')
    .update(rawBody + WEBHOOK_SECRET)
    .digest('hex');

  return signature === expectedSignature;
};

To prevent replay attacks, add a validity period to reject old webhooks and prevent duplicates.

const isReplayAttack = (event: WebhookEvent): boolean => {
  const eventTime = new Date(event.event.created_at).getTime();
  return (Date.now() - eventTime) > 5 * 60 * 1000; // 5-minute window
};

As a final safeguard against forged or stale events, use the payment gateway’s verifications endpoint to validate the transaction status.

const verifyTransaction = async (transactionId: string): Promise<boolean> => {
  try {
    const response = await axios.get(
      `https://REPLACE_WITH_PAYMENT_GATEWAY_ENDPOINT/${transactionId}/verify`,
    );
    return response.data.status === 'successful';
  } catch (error) {
    return false;
  }
};

Step 3: Configure the Logger

Use the winston library to log activities on your backend. Logs are crucial for debugging, performance monitoring, security analysis, and compliance.

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json() // Structured logging for easier analysis
  ),
  transports: [
    new winston.transports.Console(), // Log to console
    new winston.transports.File({ filename: 'webhooks.log' }) // Persist logs
  ]
});

Step 4: Handle Webhook

Use the helper functions you created earlier to process the webhook and log relevant information.

const handleWebhook = async (req: express.Request, res: express.Response) => {
  try {
    if (!validateSignature(req)) {
      logger.warn('Invalid webhook signature', { ip: req.ip });
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const event: WebhookEvent = JSON.parse(req.body.toString('utf-8'));

    if (isReplayAttack(event)) {
      logger.warn('Replay attack detected', { eventId: event.id });
      return res.status(400).json({ error: 'Stale event' });
    }

    const isValid = await verifyTransaction(event.data.id);
    if (!isValid) {
      logger.error('Transaction verification failed', { eventId: event.id });
      return res.status(400).json({ error: 'Verification failed' });
    }

    // Process event (e.g., update database)
    logger.info('Processed Payment Gateway event', {
      eventId: event.id,
      type: event.event.type
    });

    res.status(200).json({ status: 'success' });
  } catch (error) {
    logger.error('Webhook processing failed', {
      error: error instanceof Error ? error.message : 'Unknown error'
    });
    res.status(500).json({ error: 'Internal server error' });
  }
};

Step 5: Create the Webhook Route and Server

Use the middleware you created earlier to set up a route and start a server for the webhook endpoint.

// Webhook Endpoint
app.post(
  '/webhook',
  bodyParser.raw({ type: 'application/json' }), // Keep raw payload intact
  handleWebhook // Handler defined in Step 5
);

app.listen(port, () => {
  logger.info(`Webhook server started on port ${port}`);
});

While the steps above address webhook vulnerabilities like replay attacks, rate limiting, and idempotency, securing webhooks is not one-sided. Even with a strong webhook system, you may still encounter issues due to external providers or how your system interacts with them. Some challenges include:

  • Provider Downtime: If the webhook provider goes down, your system won’t receive updates, which could lead to duplicate charges or multiple order processing.
  • Payload Changes: The provider might change the webhook data format, breaking your integration.
  • Security Issues: Some providers don’t sign webhooks properly, making them easier to spoof.
  • Webhook URL Exposure: Attackers can send fake requests if your webhook URL is leaked.
  • Phantom (fake) Events: Some providers may send incorrect or duplicate events due to system bugs.

To handle these challenges, you can use Flutterwave as your payment gateway. It complements your technical implementation and helps you build a more reliable webhook system.

How Flutterwave Helps You Build a Better Webhook Service

Flutterwave provides built-in tools to help your application manage webhooks effectively. Here’s how it supports a reliable webhook system:

Webhook Manager

Flutterwave offers a dedicated webhook management panel where you can configure your webhook URL, signature, and preferences. This gives you full control over how Flutterwave processes and sends webhooks to your system, ensuring that requests come from an authorized source.

Webhook Manager

Retry Mechanism

Flutterwave lets you decide whether webhooks should retry on failure. If enabled, Flutterwave resends the webhook up to three times, with a 30-minute interval between each attempt.

Built-in Security Measures

Flutterwave includes security features to help you verify webhook authenticity:

  • Each webhook payload includes a timestamp, allowing you to check whether it’s recent.
  • Webhooks expire after 60 seconds, requiring your system to respond within that window. This improves reliability and prevents unnecessary resource usage.

Robust API Suite

Flutterwave APIs come with a security mechanism that ensures only authorized parties can access your system. If an attacker gains access to your system, Flutterwave’s rate-limiting feature helps prevent abuse and unauthorized transactions. A dedicated transaction verification API also lets you validate payment statuses as a final safeguard.

IP Whitelisting

Flutterwave gives you control over who can access your Flutterwave account to process payouts via the API. If, for any reason, an IP address that’s not whitelisted gets a hold of your webhook URL and attempts a malicious act, it won’t be successful.

Wrapping Up

A secure and reliable webhook service boosts your application's efficiency, improves user experience, and helps you avoid unnecessary costs from infrastructure and legal penalties. However, managing webhooks comes with challenges, especially since you don't have full control over external providers. That’s why choosing the right provider matters.

Flutterwave simplifies webhook management by offering:

  • Smart retry mechanism.
  • Built-in security measures.
  • IP whitelisting and more.

With these features, you can build a more resilient webhook system without unnecessary complexity. Get started with Flutterwave today.