Best Practices

Security

Building on PayMongo means handling real money and real customer data. This page covers the security model for PayMongo's Developer Tools — how API keys work, how to verify webhook authenticity, and the practices that keep your integration secure in production.

API keys

PayMongo issues two types of API keys — a public key and a secret key. Understanding what each one is for determines where it's safe to use them.

Key typePrefixSafe to expose?Use for
Public keypk_YesClient-side requests — initiating payment intents from a browser or mobile app
Secret keysk_NeverServer-side requests — creating resources, reading data, managing your integration

Your secret key has full access to your PayMongo account. Treat it like a password.

Keep secret keys on the server. Never include a secret key in client-side code, mobile app binaries, or public repositories. If a secret key is exposed, rotate it immediately from Settings → API Keys in the Dashboard — do not wait to assess the impact first.

Use environment variables. Store keys in environment variables rather than hardcoding them in your codebase. This keeps them out of version control and makes rotation straightforward.

// Never do this
const apiKey = "sk_live_xxxxxxxxxxxxxxxxxxxxxxxx";

// Do this instead
const apiKey = process.env.PAYMONGO_SECRET_KEY;

Use separate keys for each environment. PayMongo issues distinct key pairs for live and test environments. Never use a live key in development or testing — mistakes made against the live API have real consequences.

Limit key exposure across your team. Not everyone who works on your integration needs access to live secret keys. Restrict access to the smallest group necessary and audit it regularly.

Idempotency


Networks are unpredictable. A request to PayMongo might time out, a connection might drop, or your server might restart before you receive a response. Without a safety net, retrying that request could create a duplicate charge, payout, or record — a problem your customers will definitely notice.

Idempotency is that safety net. By attaching a unique idempotency key to any request that creates or modifies data, you tell PayMongo: "If you've already processed this exact request, return the original result — don't do it twice."

If you are a developer building an integration with the PayMongo API, this page is for you. No prior knowledge of idempotency is required — everything you need to know is covered here.

What is idempotency?

An operation is idempotent when performing it multiple times produces the same result as performing it once. Think of pressing an elevator button — pressing it five more times does not make the elevator arrive faster or call it five times. The outcome is the same.

In the context of the PayMongo API, idempotency means that retrying a failed or uncertain request will never create a duplicate outcome, as long as you use the same idempotency key.

What is an idempotency key?

An idempotency key is a unique string that you generate and attach to a request via the Idempotency-Key HTTP header. PayMongo uses this key to recognize when a request has been seen before and, if so, returns the cached result of the original request instead of processing it again.

⚠️

Idempotency keys are scoped to your secret API key and HTTP method. The same key used against two different endpoints, or with two different API keys, is treated as a separate, unrelated request.

How PayMongo handles idempotency keys

01 — First request

Request is processed
You send a request with a new idempotency key. PayMongo processes it normally and stores the result alongside the key.

02 — Retry request

Same key is sent again
You retry the same request using the same key — because the first attempt timed out or returned no response.

03 — Cached response

Original result is returned
PayMongo recognizes the key and returns the original result immediately. No duplicate action is taken.

Request header

HeaderTypeDescription
Idempotency-KeystringA unique string you generate per request. Recommended for all POST requests that create or modify data. Maximum 255 characters.

When to use idempotency keys

Use an idempotency key on any POST request that creates a new resource or triggers an action. You generally do not need one for GET or DELETE requests — these are either read-only or already safe to retry by nature.

Example Request

curl -X POST https://api.paymongo.com/v1/{resource} \
  -H "Authorization: Basic <base64_encoded_secret_key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: <your-unique-idempotency-key>" \
  -d '{
    "data": {
      "attributes": {
        // your request payload here
      }
    }
  }'

Webhook signature verification

Webhooks arrive at a publicly accessible endpoint — which means anyone can send a request to it. Signature verification is how you confirm a webhook was genuinely sent by PayMongo and hasn't been tampered with in transit.

PayMongo signs every webhook request using HMAC-SHA256 with a secret key tied to your endpoint. The signature is included in the Paymongo-Signature header of every request.

To verify a request, compute the expected signature from the raw request body and your endpoint secret, then compare it against the header value.

const crypto = require("crypto");

function verifyWebhook(rawBody, secret, signatureHeader) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["paymongo-signature"];

  if (!verifyWebhook(req.body, process.env.WEBHOOK_SECRET, signature)) {
    return res.sendStatus(401); // Reject unverified requests
  }

  res.sendStatus(200);
  queue.push(JSON.parse(req.body));
});

A few things to keep in mind:

Always verify before processing. Signature verification should be the first thing your handler does — before parsing the body, before touching a database, before anything else.

Use the raw request body. Any middleware that parses or modifies the request body before verification will alter the byte representation and cause legitimate requests to fail verification. Configure your framework to pass the raw body to your verification step.

Use timing-safe comparison. Standard string equality checks are vulnerable to timing attacks. Always use a constant-time comparison function like crypto.timingSafeEqual.

Rotate compromised secrets immediately. If your webhook secret is exposed, generate a new one from Developers → Webhooks in the Dashboard. Update your handler before the old secret is invalidated to avoid dropping legitimate deliveries during the transition.

HTTPS everywhere

All communication with the PayMongo API is over HTTPS — the API will reject plaintext HTTP requests outright. Apply the same standard to your own infrastructure.

Your webhook endpoint must use HTTPS. PayMongo will not deliver events to plaintext HTTP endpoints in live mode. Use a valid TLS certificate from a trusted certificate authority — self-signed certificates are not accepted.

Validate TLS on outbound requests. If your integration makes server-to-server calls, ensure TLS certificate validation is enabled. Disabling certificate validation to work around errors is a common shortcut that exposes your integration to man-in-the-middle attacks.

Protecting customer data

PayMongo handles the sensitive parts of payment data — raw card numbers never touch your servers. Keep it that way.

Never log full API responses in production. API responses may contain customer details, payment method information, and resource IDs that are sensitive in aggregate. Log only what you need for debugging, and ensure logs are access-controlled.

Never store raw card data. If your integration needs to charge a customer more than once, use PayMongo's payment method resources rather than storing card details yourself. Storing raw card data without PCI DSS compliance is both a security risk and a policy violation.

Sanitize error messages before surfacing them. PayMongo error responses are intended for your server logs, not your customers. The detail field may contain field names, parameter values, or other internals that should not be exposed in a user-facing UI. Map error codes to your own customer-friendly messages instead.

Common security mistakes

Hardcoding API keys in source code. Even in private repositories, hardcoded keys are a rotation and access control risk. Use environment variables or a secrets manager.

Skipping signature verification. An unverified webhook endpoint will process any request sent to it. This can be exploited to trigger fraudulent fulfillments or flood your handler with junk events.

Using live keys in development. Test mistakes — accidental charges, malformed requests, logic errors — should never touch real accounts. Always develop against test keys and test data.

Sharing secret keys across services. If multiple services share a key and one is compromised, all are compromised. Issue separate keys where possible and rotate them independently.

Not rotating keys after team changes. When someone with access to your live keys leaves your team, rotate those keys. Access that isn't actively revoked remains active.