Skip to content

Quickstart

This guide takes you from zero to a captured payment using the recommended webhook flow. Allow about 30 minutes.

We recommend running this guide against the sandbox environment first — no real money moves. The examples below use the production base URL; to run them in the sandbox, swap in the sandbox base URL and credentials.

Prerequisites

  • A Maytes merchant account with access to the merchant portal — see Onboarding if you don't have one yet.
  • Your client_id and client_secret, from the portal (use the sandbox pair while testing).
  • A server you can make HTTPS requests from. We recommend the backend SDK for your language, but raw HTTP works too.
  • A publicly reachable HTTPS endpoint for webhooks. For local development, a tunnel (ngrok, cloudflared) in front of your dev server works fine.

The base URL throughout this guide:

https://api.maytes.co

To test without moving real money, use the sandbox base URL instead — see Sandbox environment.

Step 1 — Get an access token

Maytes uses OAuth 2.0 client credentials. Exchange your client_id / client_secret for a short-lived access token.

bash
curl -s https://api.maytes.co/oauth/token \
  -H 'content-type: application/json' \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET"
  }'

You'll get back an access_token. Send it as a Bearer token on every merchant API call. See Authentication for caching and refresh.

Use the SDK

The backend SDKs fetch, cache, and refresh this token for you — you just pass your client_id/client_secret once. The rest of this quickstart shows raw HTTP so you can see the wire calls.

Step 2 — Subscribe your webhook endpoint

In the merchant portal (or via the subscription API), register your endpoint for the two checkout events:

bash
curl -s https://api.maytes.co/api/merchant/v1/webhooks/subscription \
  -H "authorization: Bearer $ACCESS_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "url": "https://shop.example.com/webhooks/maytes",
    "event_types": ["checkout.authorized", "checkout.voided"]
  }'

The response includes your signing_secret — shown once, store it now. Every delivery is signed with it; your handler must verify the signature before trusting a request.

Send a webhook.test event from the portal to confirm your endpoint receives, verifies, and 2xx-acknowledges a delivery before going further.

Step 3 — Implement the webhook handler

When a customer completes payment, Maytes sends checkout.authorized to your endpoint. Your handler verifies the signature, runs your final checks, and captures — synchronously, while the customer watches a processing screen in the Maytes window. The authorization is auto-voided after 2 minutes if nothing captures it, so capture inline in the handler, as below:

ts
app.post('/webhooks/maytes', express.raw({ type: 'application/json' }), async (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyMaytesSignature(raw, req.header('x-maytes-signature') ?? '', WEBHOOK_SECRET)) {
    return res.status(400).end();
  }

  const event = JSON.parse(raw);
  if (event.type === 'checkout.authorized') {
    const { checkout_uuid, merchant_order_id, amount, currency } = event.data;
    // Capture is synchronous — a 200 response means the money has moved.
    await maytes.captureCheckout(checkout_uuid, {
      expected_total_amount: amount,
      currency,
      merchant_order_id,
    });
    await fulfilOrder(merchant_order_id); // capture success = your fulfilment trigger
  }

  res.status(200).end();
});

The full recipe — deduplication, the can't-fulfil cancel path, and error handling — is in Webhooks. The verifyMaytesSignature implementation for Node, Python, and PHP is there too.

Step 4 — Create a checkout

Create a checkout for the order, with your line items and the total. return_url and cancel_url are where the customer lands when they finish or abandon — in the webhook flow that's a confirmation page, not your fulfilment trigger (capture already happened in Step 3).

bash
curl -s https://api.maytes.co/api/merchant/v1/checkouts \
  -H "authorization: Bearer $ACCESS_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "merchant_order_id": "order_1001",
    "total_amount": 4500,
    "currency": "AUD",
    "return_url": "https://shop.example.com/thanks?order=order_1001",
    "cancel_url": "https://shop.example.com/cart",
    "items": [
      {
        "item_ref": "GA-1",
        "name": "General Admission",
        "quantity": 1,
        "unit_price": 4500,
        "currency": "AUD",
        "category": "ticket",
        "custom_data": {
          "event_id": "evt_123",
          "event_start_at": "2026-12-05T19:00:00Z",
          "timezone": "Australia/Sydney"
        }
      }
    ]
  }'

Response:

json
{
  "data": {
    "checkout_uuid": "0192a8f2-...",
    "status": "created",
    "checkout_url": "https://checkout.maytes.co/0192a8f2-...",
    "expires_at": "2026-09-14T03:30:00.000Z"
  }
}

Save checkout_uuid against your order — your webhook handler and support tooling will look orders up by it. See Create a checkout for the full request (fees, discounts, tax, item categories) and the amount rules.

Step 5 — Send the customer to the Maytes window

Open checkout_url for the customer — the Maytes Checkout Button does this for you with a popup and overlay, or redirect their browser directly:

js
res.redirect(checkout.data.checkout_url);

Step 6 — Watch it complete

Pay in the Maytes window — in the sandbox, use the test card 4242 4242 4242 4242 (any future expiry, any CVC — see test cards). Then:

  1. Your endpoint receives checkout.authorized.
  2. Your handler captures — the customer's processing screen clears and they continue inside Maytes (adding the mates who share the cost) before landing on your return_url.
  3. Confirm the state:
bash
curl -s https://api.maytes.co/api/merchant/v1/checkouts/$CHECKOUT_UUID \
  -H "authorization: Bearer $ACCESS_TOKEN"
json
{ "data": { "checkout_uuid": "0192a8f2-...", "status": "captured", "amount": 4500, "currency": "AUD", "captured_at": "2026-09-14T03:31:12.000Z" } }

That's a complete payment. 🎉

Go live

  1. Run the pre-go-live checklist in the sandbox — declines, abandonment, cancel, the amount-mismatch errors, and a webhook.test against your production endpoint.
  2. Handle failures — react to the error codes (e.g. invalid_amount_arithmetic, not_capturable) rather than assuming success.
  3. Switch to production — point your calls back at https://api.maytes.co, swap in your production client_id / client_secret, and create a production webhook subscription (it has its own signing secret).

Next steps