Security Best Practices
Protect your PayDirect integration with these security recommendations for API key management, webhook verification, and production hardening.
API Key Security
Keep your API keys safe and properly scoped
- Never expose keys in client-side code. API keys should only be used in your backend. Never include them in JavaScript bundles, mobile apps, or public repositories.
- Use environment variables. Store keys as
PAYDIRECT_API_KEY=pd_test_...in your.envfile. Never commit.envto version control. - Use sandbox keys for development. Keys with the
pd_test_prefix only work in sandbox mode. Usepd_live_keys only in production environments. - Rotate keys periodically. Create new keys and revoke old ones via the dashboard or the Keys API.
- Revoke compromised keys immediately. If a key is leaked, delete it via
DELETE /api/v1/keys/:idand create a replacement.
Webhook Signature Verification
Always verify HMAC-SHA256 signatures before processing webhooks
Every webhook request includes an X-PayDirect-Signature header containing an HMAC-SHA256 hex digest of the request body, signed with your webhook's signing secret (whsec_...).
TypeScript Verification
import { PayDirectClient } from "@paydirect/sdk";
app.post("/webhooks/paydirect", (req, res) => {
const signature = req.headers["x-paydirect-signature"];
const body = JSON.stringify(req.body);
const isValid = PayDirectClient.verifyWebhookSignature(
process.env.WEBHOOK_SIGNING_SECRET,
body,
signature
);
if (!isValid) {
console.error("Webhook signature verification failed");
return res.status(401).json({ error: "Invalid signature" });
}
// Process the verified webhook...
res.status(200).json({ received: true });
});Manual Verification (Node.js)
import crypto from "crypto";
function verifyWebhook(secret: string, body: string, signature: string): boolean {
const expected = crypto.createHmac("sha256", secret).update(body).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signature, "hex")
);
}Critical: Use timing-safe comparison (e.g.
crypto.timingSafeEqualor hmac.compare_digest in Python) to prevent timing attacks. Never use === for signature comparison.Idempotency Keys
Prevent duplicate payments from retries and network failures
Always include an Idempotency-Key header when creating payments. If the same key is used again, the API returns the existing payment instead of creating a duplicate.
// Derive idempotency key from your business logic
const idempotencyKey = `order-${orderId}-payment-v1`;
const { payment } = await client.createPayment({
tokenSymbol: "USDC",
amount: "50.00",
merchantWallet: "0x...",
idempotencyKey,
});- Use deterministic keys based on your order or invoice ID
- Include a version suffix if you need to retry with different parameters
Transport Security
- All API requests must use HTTPS. HTTP requests are rejected.
- Webhook endpoints must be served over HTTPS with a valid TLS certificate.
- Pin the PayDirect base URL (
https://www.paydirect.com/api/v1) in your configuration rather than constructing it dynamically.
Wallet Address Verification
- Double-check your merchant wallet. The
merchantWalletaddress is where your net payments are forwarded. Sending to a wrong address is irreversible. - Verify it is a Base address. All PayDirect settlement happens on Base mainnet. Ensure your wallet address works on the Base network.
- Validate the address format in your code:
/^0x[a-fA-F0-9]{40}$/
Rate Limiting
The API enforces rate limits per API key: 100 requests/minute (Free), 1,000 requests/minute (Pro). Handle 429 responses gracefully:
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("retry-after") || "5");
await new Promise(r => setTimeout(r, retryAfter * 1000));
// Retry the request
}