Security
Protect your API keys, verify webhook signatures, and stay PCI-compliant effortlessly.
Security is at the core of Wajub. As an integrator, you are responsible for protecting your keys and validating incoming data. Here are the essential measures.
API key management
Never commit your keys
WAJUB_PUBLIC_KEY=pk_test.8f2b91c4d7e6a0f3
WAJUB_SECRET_KEY=sk_test.1a3b5c7d9e # backend only
WAJUB_WEBHOOK_SECRET=whsec_xyz123abc
# No hardcoded keys in code!# Never commit these files
.env
.env.*
!.env.exampleFrontend / backend separation
| Context | Acceptable key | Where? |
|---|---|---|
| Checkout page (browser) | pk_test.… or pk.… | Build environment variable |
| Backend server | pk_… + sk_… | Runtime environment variables |
| CI / tests | pk_test.… only | CI secrets |
| Mobile app | pk_… | Build config (never sk_…) |
The secret key never leaves the server
The sk_… key grants access to refunds, transfers, and sensitive operations.
It must not appear in any client code (frontend, mobile, browser
extension).
Key rotation
Plan periodic rotation and document the procedure for your team:
- Generate a new key in Dashboard > Developers > API Keys.
- Deploy the new key alongside the old one.
- Verify that everything works with the new key.
- Revoke the old key in the Dashboard.
Webhook verification
The webhook signature is your guarantee that the request truly comes from Wajub. Never rely only on the origin URL.
How it works
Wajub signs each payload with your webhook secret (whsec_…) using HMAC-SHA256.
The result is sent in the x-wajub-signature header.
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(typeof payload === 'string' ? payload : JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Use timingSafeEqual
A classic === comparison is vulnerable to timing attacks. Always use
crypto.timingSafeEqual to compare signatures.
Verification via SDK
All our SDKs expose a dedicated method. This is the recommended approach:
// SDK handles everything: parsing, timing-safe, signature format
const event = wajub.webhooks.constructEvent(
req.body, // Raw buffer (not JSON parsed!)
req.headers['x-wajub-signature'],
process.env.WAJUB_WEBHOOK_SECRET
);PCI DSS compliance
By using the Wajub-hosted payment page or Embedded Checkout, you never handle card data or sensitive Mobile Money numbers directly. This reduces your PCI scope to the simplest level (SAQ A).
What you must never do:
- Collect card numbers on your own form.
- Store raw Mobile Money identifiers without encryption.
- Log complete payment data.
Security checklist
- API keys stored in environment variables, never committed.
- Secret key (
sk_…) exclusively server-side. - Webhooks: signature verified before any processing.
- Webhooks: raw payload passed to
constructEvent(no prior parsing). - Constant-time signature comparison (
timingSafeEqual). - Idempotency on webhook processing (
event.id). - Logs without sensitive data (no keys or full numbers).
- Key rotation documented and tested.
- 2FA enabled on all Dashboard accounts.