← Back to blog
engineering7 min read

Securing Your Payment API Integration: Best Practices

Essential security practices for integrating payment APIs — covering API key management, webhook verification, CORS configuration, and test vs live environments.

Securing Your Payment API Integration: Best Practices

DGateway is a unified payment and commerce platform for Africa that lets developers accept mobile money (MTN, Airtel) and card payments through a single API. Like any payment integration, securing your DGateway implementation is critical to protecting your customers and your business.

When you integrate a payment API, you are handling your customers' money. A security vulnerability is not just a bug — it is a breach of trust that can cost you customers, revenue, and reputation. This guide covers the essential security practices every developer should follow when working with DGateway or any payment API.

API Key Management

Your API keys are the keys to your kingdom. Treat them accordingly.

Keep Secret Keys Secret

DGateway provides two types of keys: public keys and secret keys. The distinction matters.

  • Public keys (prefixed with pk_) identify your account and can be used in client-side code. They can only perform limited operations like initializing checkout sessions.
  • Secret keys (prefixed with sk_) authenticate your server and have full access to your account. They can create transactions, issue refunds, access customer data, and modify settings.

Your secret key must never appear in client-side code, public repositories, log files, or error messages. If a secret key is compromised, an attacker can drain your account.

Use Environment Variables

Never hardcode API keys in your source code. Store them in environment variables:

# .env (never commit this file)
DGATEWAY_SECRET_KEY=sk_live_your_key_here
DGATEWAY_PUBLIC_KEY=pk_live_your_key_here
DGATEWAY_WEBHOOK_SECRET=whsec_your_secret_here

Add .env to your .gitignore file. If you are using a framework like Next.js, only variables prefixed with NEXT_PUBLIC_ are exposed to the browser — keep your secret key without that prefix.

Rotate Keys Regularly

Establish a key rotation schedule. Even if you have no reason to believe your keys are compromised, rotating them periodically limits the blast radius of an undetected leak.

DGateway supports key rotation without downtime. Generate new keys in your dashboard, update your environment variables, and revoke the old keys once you have confirmed the new ones are working.

Restrict Key Permissions

If DGateway supports scoped API keys (keys with limited permissions), use them. A key that only needs to create collections should not have permission to issue refunds or modify account settings. The principle of least privilege applies here as much as anywhere.

Webhook Security

Webhooks are the most common attack vector in payment integrations. An attacker who can forge webhook requests can trick your application into fulfilling orders that were never paid for.

Always Verify Webhook Signatures

DGateway signs every webhook with your webhook secret using HMAC-SHA256. Verify this signature before processing any webhook:

const crypto = require("crypto");
 
function verifyWebhookSignature(rawBody, signature, webhookSecret) {
  const computed = crypto
    .createHmac("sha256", webhookSecret)
    .update(rawBody)
    .digest("hex");
 
  return crypto.timingSafeEqual(
    Buffer.from(signature, "utf8"),
    Buffer.from(computed, "utf8")
  );
}
 
// In your webhook handler
app.post("/webhooks/dgateway", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-dgateway-signature"];
 
  if (!verifyWebhookSignature(req.body, signature, process.env.DGATEWAY_WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }
 
  // Process the verified webhook
  const event = JSON.parse(req.body);
  handleEvent(event);
  res.status(200).send("OK");
});

Important details:

  • Use timingSafeEqual to prevent timing attacks. A regular string comparison (===) reveals how many characters matched, which an attacker can exploit to guess the signature one character at a time.
  • Verify against the raw request body, not a parsed and re-serialized version. JSON serialization is not deterministic — the signature was computed against the exact bytes DGateway sent.

Verify Transaction Status Server-Side

Never trust the webhook payload alone for critical business logic. After receiving a webhook indicating a successful payment, make a server-side API call to DGateway to verify the transaction status:

async function verifyTransaction(transactionId) {
  const response = await fetch(
    `https://api.dgateway.io/v1/transactions/${transactionId}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.DGATEWAY_SECRET_KEY}`,
      },
    }
  );
 
  const transaction = await response.json();
  return transaction.status === "successful";
}

This defense-in-depth approach means that even if an attacker somehow bypasses signature verification, they cannot forge a transaction that passes server-side validation.

CORS Configuration

Cross-Origin Resource Sharing (CORS) controls which domains can make requests to your API. Misconfigured CORS can expose your payment endpoints to abuse.

Restrict Allowed Origins

If your payment API routes are only called from your own frontend, configure CORS to only allow your domain:

const cors = require("cors");
 
app.use(
  cors({
    origin: ["https://yourdomain.com", "https://www.yourdomain.com"],
    methods: ["POST"],
    allowedHeaders: ["Content-Type", "Authorization"],
  })
);

Never use origin: "*" on payment-related endpoints. An open CORS policy allows any website to make requests to your API, potentially enabling CSRF-like attacks.

Protect Sensitive Endpoints

Payment initiation endpoints should have additional protections:

  • Rate limiting — Prevent brute-force attacks and abuse. Limit to a reasonable number of requests per minute per IP.
  • Authentication — Require user authentication before allowing payment initiation. Do not let unauthenticated users create transactions.
  • Input validation — Validate all inputs on the server. Never trust client-side validation alone. Check amounts, currencies, phone numbers, and metadata.

Test vs Live Environments

DGateway provides separate test and live environments. Using them correctly is a security practice, not just a development convenience.

Never Use Live Keys in Development

Your test keys and live keys are completely separate. Test transactions do not move real money and do not appear in your live dashboard. There is no reason to use live keys during development.

If you are testing locally, use test keys exclusively. If you are running a staging environment, use test keys. Live keys should only exist in your production environment's configuration.

Environment Isolation

Keep your test and live configurations in separate environment files:

.env.development   → test keys
.env.staging       → test keys
.env.production    → live keys

Your deployment pipeline should ensure that the correct environment file is used for each environment. A misconfiguration that deploys test keys to production is annoying but harmless. A misconfiguration that deploys live keys to a developer's laptop is a security risk.

Test the Full Security Flow

Before going live, verify that your security measures work correctly:

  1. Send a forged webhook (without a valid signature) and confirm your endpoint rejects it.
  2. Send a webhook with a tampered payload and confirm signature verification fails.
  3. Attempt to access your API from an unauthorized origin and confirm CORS blocks the request.
  4. Submit invalid input to your payment endpoint and confirm server-side validation catches it.
  5. Check your logs to ensure no API keys or sensitive data are being logged.

Logging and Monitoring

Log Events, Not Secrets

Log webhook receipts, transaction outcomes, and error events for debugging and auditing. But never log API keys, full phone numbers, or raw webhook payloads that contain customer data in plain text.

Mask sensitive fields in your logs:

function sanitizeForLog(data) {
  return {
    ...data,
    phone_number: data.phone_number
      ? `${data.phone_number.slice(0, 6)}****`
      : undefined,
  };
}

Set Up Alerts

Configure alerts for:

  • Webhook signature verification failures (potential attack)
  • Unusual transaction volumes (potential fraud)
  • Failed API authentication attempts (potential key compromise)
  • High error rates on payment endpoints (potential system issues)

Summary

Security is not a feature you add at the end. It is a discipline you maintain from the first line of code. When it comes to payment integrations, the stakes are high and the best practices are well-established:

  • Protect your API keys like passwords.
  • Verify every webhook signature.
  • Validate transactions server-side.
  • Configure CORS restrictively.
  • Isolate test and live environments.
  • Log carefully and monitor continuously.

Get these fundamentals right, and your payment integration will be secure by design — not by luck.