Skip to main content

Overview

Webhooks are HTTP callbacks sent from InventPay to your server. Since they can trigger critical business logic (like order fulfillment), securing your webhook endpoint is essential to prevent fraud and unauthorized access.
Never skip security measures. An unsecured webhook endpoint can be exploited to trigger fraudulent order fulfillment or other malicious actions.

Security Threats

Understanding potential threats helps you implement appropriate defenses:

Replay Attacks

Attacker resends captured webhook to trigger duplicate actions

Forgery Attacks

Attacker sends fake webhooks pretending to be InventPay

Man-in-the-Middle

Attacker intercepts and modifies webhooks in transit

Denial of Service

Attacker floods your endpoint with requests

Essential Security Measures

1. Signature Verification (Required)

Every webhook includes an HMAC-SHA256 signature in the X-Webhook-Signature header. Always verify this signature before processing webhooks.

How Signature Verification Works

Implementation

const crypto = require("crypto");

function verifyWebhookSignature(payload, signature, secret) {
  // Compute expected signature
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(JSON.stringify(payload))
    .digest("hex");

  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your webhook handler
app.post("/webhook", (req, res) => {
  const signature = req.headers["x-webhook-signature"];
  const isValid = verifyWebhookSignature(
    req.body,
    signature,
    process.env.WEBHOOK_SECRET
  );

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

  // Process webhook...
});
Critical: Use the raw request body for signature verification, not the parsed JSON object. Body parsers may modify the structure.

Common Signature Verification Mistakes

Wrong:
const signature = crypto
  .createHmac('sha256', secret)
  .update(req.body) // req.body is already parsed object
  .digest('hex');
Right:
const signature = crypto
  .createHmac('sha256', secret)
  .update(req.rawBody) // Use raw string
  .digest('hex');
Make sure you’re using the webhook secret from your dashboard, not your API key.
Wrong:
if (signature === expectedSignature) // Vulnerable to timing attacks
Right:
if (crypto.timingSafeEqual(
  Buffer.from(signature),
  Buffer.from(expectedSignature)
))

2. HTTPS Only (Required)

Never accept webhooks over HTTP. Always use HTTPS to prevent:
  • Man-in-the-middle attacks
  • Eavesdropping
  • Payload tampering
// Reject HTTP requests
if (req.protocol !== "https") {
  return res.status(403).json({ error: "HTTPS required" });
}
InventPay will reject webhook URLs that use HTTP protocol. Only HTTPS URLs are accepted.

3. Idempotency (Required)

Implement idempotency to handle duplicate webhook deliveries safely. Use the X-Webhook-ID header or payment ID to track processed webhooks.

Why Idempotency Matters

Webhooks may be delivered multiple times due to:
  • Network retries
  • Timeout retries
  • Manual redelivery
Without idempotency, you might:
  • Fulfill the same order twice
  • Charge customer twice
  • Send duplicate notifications

Implementation

// Store processed webhook IDs in database
async function handleWebhook(req, res) {
  const webhookId = req.headers["x-webhook-id"];

  // Check if already processed
  const alreadyProcessed = await db.webhookDeliveries.findOne({
    webhookId: webhookId,
  });

  if (alreadyProcessed) {
    console.log("Webhook already processed:", webhookId);
    return res.status(200).json({ received: true, duplicate: true });
  }

  // Process webhook
  await processWebhookEvent(req.body);

  // Mark as processed
  await db.webhookDeliveries.create({
    webhookId: webhookId,
    processedAt: new Date(),
    event: req.body.event,
    paymentId: req.body.data.paymentId,
  });

  res.status(200).json({ received: true });
}
Store webhook IDs for at least 7 days to handle retries. After 7 days, InventPay stops retrying failed webhooks.

4. IP Whitelisting (Optional)

For additional security, whitelist InventPay’s IP addresses:
52.89.214.238
34.212.75.30
54.218.53.128

Implementation

const INVENTPAY_IPS = ["52.89.214.238", "34.212.75.30", "54.218.53.128"];

app.post("/webhook", (req, res) => {
  const clientIP =
    req.headers["x-forwarded-for"] || req.connection.remoteAddress;

  if (!INVENTPAY_IPS.includes(clientIP)) {
    return res.status(403).json({ error: "IP not whitelisted" });
  }

  // Process webhook...
});
IP addresses may change. We recommend signature verification as the primary security method, with IP whitelisting as secondary defense.

Implement rate limiting to prevent abuse:
const rateLimit = require("express-rate-limit");

const webhookLimiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 100, // Max 100 requests per minute
  message: "Too many webhook requests",
  standardHeaders: true,
  legacyHeaders: false,
});

app.post("/webhook", webhookLimiter, handleWebhook);

Reject webhooks that are too old to prevent replay attacks:
function isRecentWebhook(timestamp, maxAgeMinutes = 5) {
  const webhookTime = new Date(timestamp);
  const now = new Date();
  const ageMinutes = (now - webhookTime) / 1000 / 60;

  return ageMinutes <= maxAgeMinutes;
}

app.post("/webhook", (req, res) => {
  const timestamp = req.headers["x-webhook-timestamp"];

  if (!isRecentWebhook(timestamp, 5)) {
    return res.status(400).json({ error: "Webhook too old" });
  }

  // Process webhook...
});

Additional Security Measures

Authentication Tokens

Add custom authentication to your webhook URL:
https://yourapp.com/webhook?token=your-secret-token
app.post("/webhook", (req, res) => {
  if (req.query.token !== process.env.WEBHOOK_AUTH_TOKEN) {
    return res.status(401).json({ error: "Invalid token" });
  }

  // Process webhook...
});

Firewall Rules

Configure firewall to only accept traffic from InventPay IPs:
# UFW (Ubuntu)
ufw allow from 52.89.214.238 to any port 443
ufw allow from 34.212.75.30 to any port 443
ufw allow from 54.218.53.128 to any port 443

Separate Endpoint

Use a dedicated subdomain or path for webhooks:
✅ https://webhooks.yourapp.com/inventpay
✅ https://api.yourapp.com/webhooks/inventpay
❌ https://yourapp.com/webhook

Request Size Limits

Limit webhook payload size to prevent DoS:
app.use("/webhook", express.json({ limit: "100kb" }));

Security Checklist

Before going live, verify:
  • Using HMAC-SHA256 with correct secret
  • Verifying with raw request body
  • Using constant-time comparison
  • Webhook URL uses HTTPS - [ ] Valid SSL certificate - [ ] Rejecting HTTP requests
  • Tracking processed webhook IDs - [ ] Handling duplicates gracefully - [ ] Using database or cache
  • Implemented rate limits - [ ] Appropriate limits configured - [ ] Returning proper error codes
  • Logging all webhook deliveries - [ ] Logging security violations - [ ] Monitoring for suspicious activity
  • Graceful error handling
  • Returning appropriate status codes
  • Not exposing sensitive errors

Secret Management

Storing Secrets Securely

Environment Variables

bash INVENTPAY_WEBHOOK_SECRET=your_secret_here

Secret Manager

Use AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault

Never in Code

❌ Never hardcode secrets in source code

Never in Logs

❌ Never log webhook secrets

Rotating Secrets

Periodically rotate your webhook secret:
1

Generate New Secret

Create new secret in dashboard
2

Update Application

Deploy new secret to your servers
3

Test

Verify webhooks work with new secret
4

Revoke Old Secret

Remove old secret from dashboard

Monitoring and Alerts

What to Monitor

  • Failed signature verifications
  • Unusual webhook volume
  • Old timestamp attacks
  • Repeated webhook IDs
  • IP address mismatches

Setting Up Alerts

// Alert on suspicious activity
if (failedVerifications > 5) {
  alertSecurityTeam("Multiple failed webhook verifications");
}

if (oldTimestamp) {
  alertSecurityTeam("Potential replay attack detected");
}

Incident Response

If you suspect a security breach:
1

Rotate Secrets Immediately

Generate new webhook secret in dashboard
2

Review Logs

Check for unauthorized webhook deliveries
3

Verify Orders

Audit recent orders for fraudulent fulfillment
4

Contact Support

Email [email protected] with incident details
5

Implement Additional Security

Add extra security layers based on findings

Next Steps