Skip to content

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.

  1. 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_…"
    
  2. 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" }
      }'
    
  3. III.

    Learn the outcome from a webhook

    When the payment succeeds we send payment_intent.succeeded to 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
The signature header

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.