Appearance
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_idandclient_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.coTo 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:
- Your endpoint receives
checkout.authorized. - 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. - 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
- Run the pre-go-live checklist in the sandbox — declines, abandonment, cancel, the amount-mismatch errors, and a
webhook.testagainst your production endpoint. - Handle failures — react to the error codes (e.g.
invalid_amount_arithmetic,not_capturable) rather than assuming success. - Switch to production — point your calls back at
https://api.maytes.co, swap in your productionclient_id/client_secret, and create a production webhook subscription (it has its own signing secret).
Next steps
- Read Webhooks for signature verification, delivery semantics, and secret rotation.
- Read Capture & order status for the status lifecycle, cancel, and idempotency.
- Build richer carts with line items, fees, discounts, and tax.
- Drop the checkout button onto your online store.
