M
MAXFIdeveloper hub
DocsQuickstart

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.

Test mode is the default.
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.

.env.local
# 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.

POST /v2/payments/checkout
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.

← 200 OK · 412 ms
{
  "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.

POST /your/webhook
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.

Test cards · sandbox
# 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.