Skip to main content
All articles
Technical12 min read

Direct checkout API: building your own card form in four calls

Direct checkout API: your own card form in four calls, authenticated by the session and its vk key, with the 3-D Secure return, idempotency and PCI DSS impact.

The direct checkout API is the option that lets you build your own card form instead of redirecting the buyer to the hosted payment page. It comes down to four calls (verify the session, submit the card, confirm the 3-D Secure return, read the payment status), authenticated by the session and its single-use verify key, never by your API key. It has a cost you should look at squarely: your systems handle card data and enter the PCI DSS compliance scope. This guide covers when it is justified, how to integrate it step by step, and what it changes for you.

Direct checkout, in one definition

ChariPay is a Moroccan payment platform, a PSP operated by Chari Money, a payment institution licensed by Bank Al-Maghrib. Its API offers two ways to collect a card payment from a checkout session.

Direct checkout lets you build your own card form: session verification, card submission, 3-D Secure return, payment status. These calls are not authenticated by your API key but by the session and its single-use verify key (vk) — never send your API key there — and they put you inside the card-data compliance scope.

The difference from the hosted checkout fits in two sentences. With the hosted page, you redirect the buyer to a page run by ChariPay, where they enter their card and go through 3-D Secure; you never see the number. With direct checkout, your form collects the card, and your server makes the four calls described below, server-to-server.

Custom card form or hosted page: the decision grid

Direct checkout only makes sense if you have a specific reason not to use the hosted page. Before writing a line of code, compare point by point.

CriterionHosted checkoutDirect checkout
Who sees the card numberNobody on your side: the customer enters it on the hosted pageYour form and every system the data passes through
Compliance scopeThe platform's PCI DSS Level 1 certified scope; your questionnaire is reduced, to be confirmed with your advisor or your acquirerYou enter the card-data compliance scope
Integration workCreate the session, redirect, receive the webhookCreate the session, then build and maintain four calls, the errors and the 3-D Secure return
Customizationconfig.frontend: theme, logo, company nameYour form, entirely yours
3-D SecureHandled by the pageHandled by you: redirect, then confirm the return
Order confirmationSigned webhookSigned webhook, as with the hosted page

If all you need is your colors and logo, the hosted page already covers it: the checkout session uses your colors and your logo. The reasons that justify direct checkout are rarer: a payment flow fully embedded in your product, for instance, and a team ready to carry the compliance that comes with it. If you are still weighing a payment link against a session, the comparison is in payment link or checkout; for an iOS or Android app, the recommended architecture is described in payments in a mobile app.

Still have a specific reason? Run the four calls in the sandbox before you decide, and compare with the hosted page in the same account: Start in test mode. Three fields (name, work email, company), an activation link received by email to choose your password, then the portal, where you create your own test key after re-authenticating; the full key is shown only once. Pay with the test card 4918 9141 0719 5005, CVV 123, any future expiry date, 3-D Secure code 555: only this card is accepted, and no real money moves. It is free, with no time limit and no approval to wait for.

First things first: a session created by your server

Direct checkout does not replace the checkout session: it consumes it. An order becomes a session: you create the session server-side, with your API key, through POST /v1/payment-sessions (amount, orderId, customer details, return and notification URLs, metadata). The response (201, or 200 on replay with the same externalId) carries five fields and nothing else:

json
{
  "sessionId": "ps_5Kd0Rn",
  "sessionToken": "st_…",
  "verifyKey": "vk_5Kd0Rn",
  "expiresAt": "2026-10-05T10:15:00Z",
  "checkoutUrl": "https://…/checkout/ps_5Kd0Rn"
}

For the hosted page, you would redirect the buyer to checkoutUrl. For direct checkout, you keep two values: sessionId and verifyKey, which POST /checkout/verify expects under the name vk. Your API key stays on your server, and your server is also what makes the /checkout/* calls, server-to-server: your form collects the card and passes it to your server. Each session has a limited lifetime (72 hours by default, expiresAt to shorten it) and is consumed only once, unless you created it as reusable (singleUse: false).

The four calls, in order

The Direct checkout module of the API docs details each call. Here is the sequence, on the single base URL https://api-psp.charipay.ma.

  1. 1Verify the session: POST /checkout/verify, with sessionId and vk in the body. The response gives you the display context: amount, currency, branding, expiry. This call is mandatory before any card submission; on a single-use session, the vk is consumed by this first verification.
  2. 2Submit the card: POST /checkout/submit, with sessionId and the card data. The response carries an operationId, a reference and a status: either a final status, or PENDING_3DS with a redirectionUrl.
  3. 3Confirm the 3-D Secure return: POST /checkout/return, with sessionId and the operationId from step 2, once the buyer has completed authentication. The response gives you the final status and your session's accept or decline redirectUrl.
  4. 4Read the payment status: GET /checkout/payments/{reference}, for display. The response gives the status, paid, the amount and the currency, with no personal data.
CallRoleAuthenticationWhat you keep
POST /checkout/verifyOpen the session for paymentsessionId and vkAmount, currency, branding, expiry
POST /checkout/submitSend the card dataSession already verified; Idempotency-Key if the session is reusableoperationId, reference, status, redirectionUrl
POST /checkout/returnCorrelate the 3-D Secure returnsessionId and operationIdFinal status, redirectUrl
GET /checkout/payments/{reference}Display the statusNo key, no personal datastatus, paid

The verification, in curl:

bash
# No X-CHARI-PAY-API-KEY header: the session and its vk authenticate this call.
curl -X POST 'https://api-psp.charipay.ma/checkout/verify' \
  -H 'Content-Type: application/json' \
  -d '{
    "sessionId": "ps_5Kd0Rn",
    "vk": "vk_5Kd0Rn"
  }'

We deliberately publish no example of a submit call: it is the one that carries a card number, and it belongs only in code whose compliance you control.

Authentication: the session and the vk key, never your API key

In direct checkout, the /checkout/* calls are made by your server, server-to-server; on the hosted checkout, ChariPay's payment page makes them itself. In both cases they are authenticated by the session and its vk, never by your API key: they are the exception to the X-CHARI-PAY-API-KEY header rule. A key is a secret: never in the browser, a mobile app, a git repository or a screenshot.

When session authentication fails, the API answers 401 INVALID_TOKEN: a vk that is invalid or already consumed on verify, or a card submission on a session that was not verified first. Three other responses belong to the session's lifecycle:

  • 410 SESSION_EXPIRED: the session is past its expiresAt; create a new one server-side.
  • 409 SESSION_ALREADY_CONSUMED: the session has already been paid — do not submit it again; for a new payment, create a new session, and to give money back, issue a refund.
  • 409 SESSION_NOT_ACTIVE: the session was canceled — create a new one if the order still has to be paid.

What produces these states (lifetime, the buyer's return, cancellation) is explained in checkout session expiry and return.

The 3-D Secure return and confirmation by webhook

Card payments go through 3-D Secure authentication: the cardholder confirms with their bank, which protects the merchant against most unpaid charges and disputes. With direct checkout, orchestrating that moment is your job.

  1. 1submit answers PENDING_3DS with a redirectionUrl: the buyer must open it to authenticate with their bank.
  2. 2When the buyer comes back, your server calls POST /checkout/return with the sessionId and the operationId: it gets the final status and the redirectUrl to follow.
  3. 3To display the result, read GET /checkout/payments/{reference}.

What the browser shows is not enough to confirm an order. Confirm the order when the webhook arrives, never on the redirect alone: the redirect says the buyer came back, not that the payment is secured. The signed webhook reaches your webhook endpoint (and the session's notification URL if you declared one), exactly as with the hosted page. Find your order by your metadata, echoed verbatim in the event; for a session, the event also carries your orderId.

Idempotency: never submit twice

A double click on "Pay", a connection dropping at the wrong moment, a user reloading the page: direct checkout must charge exactly once whatever happens. The Idempotency-Key header is required on POST /checkout/submit for a reusable session (singleUse: false) and recommended for a single-use one. Replaying the same value returns the first result; the same key with a different body answers 409 IDEMPOTENCY_CONFLICT. On a reusable session, a missing or oversized key answers 400 INVALID_IDEMPOTENCY_KEY.

Three practical rules:

  • One key per payment attempt, generated when the buyer submits the form and kept until the response arrives, so you can replay it unchanged after a network drop.
  • A disabled button from the first click until submit responds.
  • A replay, not a new submission: after a 5xx, replay with the same key; 5xx errors are on our side.

Every error follows the same envelope:

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "amount: must be greater than 0"
  },
  "correlationId": "b0c1e2d3-4f56-7890-abcd-ef0123456789"
}

Test the code, never the message: the first is stable, the second may change. Keep the correlationId in your logs; it lets the team find a specific request.

What direct checkout changes for PCI DSS

PCI DSS is the card data security standard published by the PCI Security Standards Council, designed, as the Council describes it, to strengthen the security of payment card account data and to encourage the wide adoption of consistent data security measures worldwide. Level 1 is the most demanding.

The platform is PCI DSS Level 1 certified, a certification renewed every year; it applies to the platform, not to your site. With the hosted page, your customer enters their card on the hosted checkout, within a PCI DSS Level 1 certified scope: you never see a card number, and your self-assessment questionnaire is reduced accordingly — to be confirmed with your advisor or your acquirer. With direct checkout, the situation is reversed: your form collects the card, your systems pass it on, and you enter the card-data compliance scope. The obligations that follow are yours, to be assessed with your advisor before going live.

The merchant side of that scope is covered in PCI DSS for merchants.

Frequently asked questions

What is direct checkout?

It is the way to collect a card payment with your own form, instead of redirecting the buyer to the hosted page. Four calls follow one another: session verification, card submission, 3-D Secure return, payment status. They are authenticated by the session and its single-use verify key, and they put you inside the card-data compliance scope.

What is the difference between direct checkout and hosted checkout?

With hosted checkout, the buyer enters their card on a page run by ChariPay, in your colors, and you never see the number. With direct checkout, your form collects the card and your server makes the four calls, server-to-server, along with the 3-D Secure return and the errors. In both cases, the order is confirmed on the signed webhook.

Should I send my API key to the direct checkout calls?

No, never. The /checkout/* endpoints are authenticated by the session and its single-use verify key, the vk, returned as verifyKey when the session is created. In this flow, your API key is only used to create the session, server-side. It must never appear in a browser, a mobile app, a git repository or a screenshot.

Does direct checkout skip the 3-D Secure step?

No. Card payments go through 3-D Secure authentication, whether the form is yours or the hosted page. When the bank requires it, submit answers PENDING_3DS with a redirectionUrl the buyer must open; when they come back, your server calls POST /checkout/return to get the final status. Your interface has to plan for that trip through the bank.

Does direct checkout change my PCI DSS obligations?

Yes. Your form collects the card data and your systems pass it on: you enter the card-data compliance scope. The PCI DSS Level 1 certification applies to the platform, not to your site. With the hosted page, your self-assessment questionnaire is reduced; with direct checkout, have your advisor assess your obligations before going live.

What happens if the customer clicks "Pay" twice?

With an Idempotency-Key on POST /checkout/submit, the second submission replays the first result instead of charging twice; the same key with a different body answers 409 IDEMPOTENCY_CONFLICT. The header is required on a reusable session and recommended otherwise. A session that has already been paid answers 409 SESSION_ALREADY_CONSUMED. Also disable the button after the first click.

What does the 401 INVALID_TOKEN error mean on direct checkout?

That the session could not be authenticated. On POST /checkout/verify, the vk is invalid or already consumed (on a single-use session, it works only once). On POST /checkout/submit, the session was not verified first. Check the order of your calls and the value you send, or create a new session server-side.

Next step

Try the hosted page first, since it already uses your colors and your logo, then direct checkout in the sandbox with the test card: Start in test mode. If you choose direct checkout, have your advisor validate your PCI DSS scope before going live. The Direct checkout module of the API docs and PCI DSS for merchants will take you further.

Sources

Written by ChariPay team.

Read next

Guide5 min read

Payment link or checkout: which to pick

Payment link or hosted checkout: two ways to get paid online, two very different workloads. The criteria that decide — volume, amount, team, customisation.

Ready to try it yourself?

Create your account and take test-mode payments today: it is free, with no approval to wait for. A question? Our team supports merchants and developers alike.

  • Free, no commitment
  • Test mode from sign-up
  • No approval to wait for