Quickstart
You'll have a real payment flow in 4 minutes. We'll create a checkout session, redirect a customer, receive a webhook, and verify the signature. No sales call. No credit card. sk_test_… only.
sk_test_ keys never touch real acquirers. Cards aren't charged. Webhooks are signed identically to live, so your code stays the same.1. Get your API key
Go to your merchant portal → Developer Hub, create a test key pair, and copy the secret. Never expose it client-side.
# 1. Grab your test secret key from https://maxfi-portal.exezine.az export MAXFI_SECRET_KEY="sk_test_2b3f9e..."
2. Create a checkout session
One POST creates a hosted, branded payment page. Pass Idempotency-Key and you're safe to retry from any queue.
curl https://maxfi-api.exezine.az/v2/payments/checkout \ -H "Authorization: Bearer $MAXFI_SECRET_KEY" \ -H "Idempotency-Key: order_42_attempt_1" \ -H "Content-Type: application/json" \ -d '{ "amount": 4900, "currency": "USD", "description": "Pro plan — annual", "success_url": "https://yourapp.com/success?id={CHECKOUT_SESSION_ID}", "cancel_url": "https://yourapp.com/pricing", "webhook_url": "https://yourapp.com/maxfi/webhook" }'
The response gives you a checkout_url — redirect the customer there. That's it on the create side.
{
"id": "cs_mo2hwnfb_044b0e14",
"checkout_url": "https://maxfi-api.exezine.az/checkout/cs_mo2hwnfb_044b0e14",
"expires_at": "2026-04-17T07:30:00Z",
"amount": 4900, "currency": "USD",
"status": "open", "livemode": false
}3. Handle the webhook
We POST to webhook_url when the payment succeeds, fails, or needs 3DS action. Verify the X-MAXFI-Signature header (HMAC-SHA256) — anything else is a 401.
import crypto from "node:crypto"; export default function handler(req, res) { const sig = req.headers["x-maxfi-signature"]; const expected = "sha256=" + crypto .createHmac("sha256", process.env.MAXFI_WEBHOOK_SECRET) .update(req.rawBody).digest("hex"); if (sig !== expected) return res.status(401).end(); const event = JSON.parse(req.rawBody); switch (event.type) { case "payment.succeeded": fulfillOrder(event.data.payment_id); break; case "payment.failed": notifyCustomer(event.data.payment_id, event.data.error_message); break; } res.status(200).end(); }
4. Test with deterministic cards
12 test cards cover every behaviour you need. Each maps to the same outcome every time — no flaky CI.
# 1. Open your checkout_url in a browser # 2. Enter card 4242 4242 4242 4242 # Any future expiry · Any CVC # 3. Watch the webhook hit your endpoint # # Decline test: 4000 0000 0000 0002 # 3DS challenge: 4000 0000 0000 3220 # Insufficient funds: 4000 0000 0000 9995 # All 12 cards in your dashboard → Sandbox tab.
Going live
Swap sk_test_ for sk_live_. That's the entire diff. Same endpoint, same signature, same shape. Your tested code goes to prod without further changes.