Developers
The API, on one page
A REST API in JSON for card and cash payments in Morocco. Your test keys arrive with your account, and the docs carry every sample in 10 languages.
I. Quickstart
A first payment, in three steps
In test mode: no real money moves, and the card page is a simulation.
I.
Get your test key
Sign up: your test secret key is shown once, at that moment. Keep it in an environment variable on your server.
export APX_SECRET_KEY="apx_sk_test_…"II.
Create a checkout session
An amount in centimes, the currency, the ways to pay, and where to send your customer back. The answer holds the page’s address: redirect your customer to it.
curl https://pay-api.adpromax.com/v1/checkout/sessions \ -u "$APX_SECRET_KEY:" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: checkout-order-1042" \ -d '{ "amount": 25000, "currency": "mad", "payment_method_types": ["card"], "success_url": "https://shop.example.com/thanks", "cancel_url": "https://shop.example.com/cart", "metadata": { "order_id": "1042" } }'III.
Learn the outcome from a webhook
When the payment succeeds we send
payment_intent.succeededto your endpoint. Check the signature on the raw body, then fulfil the order.Coming back to your success page is not proof of payment: deliver on the webhook.
import { Buffer } from 'node:buffer' import { createServer } from 'node:http' import { verifySignature } from './verify-signature.mjs' createServer(async (request, response) => { const chunks = [] for await (const chunk of request) chunks.push(chunk) // The exact bytes: verify them before parsing. const rawBody = Buffer.concat(chunks) const header = request.headers['x-adpromaxpay-signature'] ?? '' const secret = process.env.APX_WEBHOOK_SECRET if (!verifySignature(rawBody, header, secret)) { response.writeHead(400).end() return } const event = JSON.parse(rawBody.toString('utf8')) if (event.type === 'payment_intent.succeeded') { const paymentIntent = event.data.object // Fulfil the order that holds this payment intent, once. console.log('paid', paymentIntent.id) } response.writeHead(200).end() }).listen(4242)
II. The objects
Six objects, named for what they are
Each one has an ID that starts with its kind, so a log line says what it is about.
pi_…
Payment intent
One payment, from its creation to its outcome. It moves through a few statuses, and your code reads the status instead of guessing.
cs_…
Checkout session
A visit to the hosted payment page for one order. It holds the page’s address and the payment intent it pays.
cus_…
Customer
Who pays: an email, a name, a phone number. Optional, and it groups a person’s payments.
re_…
Refund
Money sent back for a card payment, in full or in part.
evt_…
Event
A record of something that happened. Events are what your webhooks deliver.
we_…
Webhook endpoint
An address on your server, and the events it wants.
III. Webhooks
Signed, and sent again
A webhook is an HTTPS request we send to your server when something happens. Every delivery is signed, and one your server did not answer is sent again.
- A signature in one header: an HMAC-SHA256 of the time and the raw body
- A delivery older than five minutes is refused, so a captured one cannot be replayed
- Retried on a schedule that runs for about three days
- Every attempt is listed in the dashboard, with a button to send it again
- A sample event to check your address, in test mode
X-AdProMaxPay-Signature: t=1790851200,v1=d2e39688034d…9a9879e
IV. Idempotency
Send it again, not twice
A network can fail after we did the work and before you read the answer. Send the same request with the same Idempotency-Key and you get the first answer back: nothing runs a second time.
- Required on every request that moves money
- Kept for 24 hours, for each organization and each mode
- A replayed answer says so, in a header
V. Errors
Errors you can act on
A request that fails answers an error object. Your code branches on its type and its code; the message is for people.
- Seven types, from a wrong request to a declined card
- A stable code for each reason, with its own page in the docs
- A request ID on every answer, to find it in the request logs
- Messages in English, French or Arabic, as your request asks
VI. Limits
Limits you can see
The API limits how many requests an organization sends per minute, so that one integration’s burst never slows the others. Test mode has its own.
- Three headers on every answer say where you stand
- A refusal tells you how long to wait
- The dashboard shows the limits that apply to you, and lets you ask for more
VII. Keys
Keys, test and live
A key tells the API which organization you are and which mode you work in. Test keys work as soon as you sign up; live keys work once your account is activated.
- A secret key is shown once, when it is created
- Roll a key, and the old one keeps working for as long as you choose
- A test key sees test data only, and a live key live data only
VIII. Samples
Every sample, in 10 languages
Choose one in the docs and every page follows.
- curl
- Node.js
- TypeScript
- Python
- PHP
- Ruby
- Go
- Java
- C#
- Rust
Get your test keys, send a first request
They arrive with your account. No real money moves in test mode.