Skip to main content

Checkout Sessions Integration Guide

Checkout Sessions let you redirect users to dhmad.tn to perform critical escrow actions—signing contracts, accepting payments, completing deliveries, opening disputes, or handling cancellations—without building these flows yourself. This guide walks you through the full integration.

Introduction

When your application needs a user to perform an escrow action that requires DHMAD’s hosted UI (e.g., contract signing, payment, proof upload/review, completion), you:
  1. Create a checkout session via the API
  2. Redirect the user to the session URL
  3. The user completes the action on dhmad.tn
  4. DHMAD redirects the user back to your app with the result
This approach is similar to Stripe Checkout: you get a hosted, secure flow with minimal integration effort.

Step-by-Step Integration

Step 1: Configure Allowed Redirect URLs

Before creating any checkout sessions, add your redirect URLs in the Developer Dashboard under Settings. Only URLs in this list will be accepted as redirectUrl when creating sessions. Examples:
  • https://myapp.com/checkout/callback
  • https://myapp.com/escrow/complete
  • http://localhost:3000/callback (for local development)
In production, redirect URLs must use HTTPS. http://localhost is allowed for development only.

Step 2: Create a Checkout Session via API

Call POST /api/v1/escrows/:id/sessions with:
  • action — The action type (e.g., sign_contract, accept_pay, submit_proof, review_proof, complete, add_funds, refund_funds)
  • targetUserEmail — Email of the user who will perform the action
  • redirectUrl — Your callback URL (must match an allowed URL)
  • metadata (optional) — Key-value pairs for your own tracking
You receive sessionId, url, and expiresAt. Redirect the user to url. If the action is already done (e.g. the user has already signed the contract, or payment is already completed), the API returns 400 with an error object: error.code, error.message (for developers), and error.user_message (for end users). Check error.type === 'business_rule_violation' and display error.user_message in your UI instead of redirecting to checkout. See Create Checkout Session for all error codes.

Step 3: Redirect the User to the Session URL

Send the user to the url returned from the create session call. For example:
The user will see the DHMAD checkout page, log in if needed, and complete the action.

Step 4: Handle the Redirect Callback

When the user finishes (or abandons) the flow, DHMAD redirects them to your redirectUrl with query parameters:
  • session_id — The session ID
  • status — One of: completed, expired, cancelled
Example callback URL:
Parse these parameters and update your UI. Optionally call GET /api/v1/sessions/:sessionId to verify and get full session details.

What happens inside checkout

The checkout page shows a summary of the escrow: amount, platform service fee (DHMAD’s fee), and, if the escrow was created with a developer fee, the application / app fee (with your app name when configured). Which party pays those fees follows feeBearer ("seller" default or "buyer"). Use buyerFundingTnd for the amount the buyer actually pays.

Instant escrows — guest payment (accept_pay, no DHMAD login)

For escrows created with mode: "instant" and a checkout session action accept_pay, if the buyer is not logged into DHMAD they can pay without creating an account. They pick a payment method (e.g. bank transfer, D17, Flouci, PayPal), complete the payment using the instructions shown, and upload a proof file. An admin must approve the proof; only then does the escrow move to paid and your app should rely on escrow.status.updated (and related webhooks) as usual.
  • Funds are not held in a DHMAD buyer wallet for this path until the proof is approved; cancellation and refunds follow DHMAD’s guest-checkout rules (your app should not assume an automatic wallet refund to the buyer).
  • Sandbox: the flow may complete without a proof file for faster testing.
  • Seller cancellation before approval: pending proofs for that escrow are closed automatically; you may receive escrow.deposit_proof.rejected (with a system reason) and escrow.status.updated when the escrow becomes cancelled. The checkout session may be marked cancelled. Treat webhooks as the source of truth, not only the browser redirect.

Standard flow (logged-in buyer or non-instant escrow)

When the user is on dhmad.tn/checkout and completes Accept and pay under the standard path (e.g. standard escrow mode, or the buyer is signed in and uses the wallet flow), DHMAD may require them to complete additional steps before the action succeeds:
  • Email or phone verification: If the buyer has not verified their email or phone, they see an inline popup on the same page to enter the verification code (OTP). We send the code when the popup opens; after they enter it and verify, they can click Confirm & Pay again. No redirect to profile.
  • KYC (identity verification): If the buyer has not completed KYC, they see an inline Verify identity (KYC) block on the same page. They can start verification (opens in a new tab); after completing it, they return to the checkout page and can click Confirm & Pay again. No redirect away from checkout.
  • Insufficient balance: If the buyer’s balance is too low, they see an inline top-up option on the same page. After topping up, they click Confirm & Pay again.
The user is only redirected back to your redirectUrl with session_id and status when they complete the action, cancel, or the session expires. You do not receive error codes (e.g. KYC_REQUIRED) in your redirect callback—those cases are handled entirely on DHMAD. For all error codes returned when creating a session (e.g. contract already signed, payment already done, SELLER_NOT_READY when the seller has not completed pre-account identity verification), see Create Checkout Session — Error Responses.

Step 5: Verify with Webhooks

Treat webhooks as the source of truth. When a user completes an action via a Checkout Session, DHMAD emits webhooks for the underlying events (e.g., contract.signed, escrow.paid, escrow.completed). Use webhooks to update your backend state; use the redirect for immediate UI feedback.

Complete Code Example (Node.js / Express)

Security Best Practices

Validate Redirect Params

When handling the callback, validate that session_id matches a session you created. Don’t trust arbitrary query params.

Use Webhooks

Always process webhooks for escrow state changes. Redirect params are for UX; webhooks are for backend truth.

HTTPS in Production

Use HTTPS for all redirect URLs in production. Never use http:// except for localhost during development.

Protect API Keys

Keep API keys server-side only. Never expose them in client-side code or public repositories.

FAQ

What happens if the session expires?

Sessions expire 30 minutes after creation. If the user does not complete the flow in time, they will be redirected with status=expired. Create a new session and redirect the user again if needed.

Can I create multiple sessions for the same escrow?

Yes. You can create multiple sessions for different actions or for retries. Each session has a unique sessionId and is single-use.

Can the same user complete multiple sessions?

Yes. Each session is independent. A user can complete one session (e.g., sign contract) and later complete another (e.g., accept & pay) for the same escrow.

What if the user closes the browser before completing?

If the user abandons the flow, they may be redirected with status=cancelled or may not reach your callback at all. Handle both cases in your UI. The session remains pending until it expires or is completed.

Do I need to handle the redirect if I use webhooks?

Yes. The redirect provides immediate feedback to the user (e.g., “Payment successful”). Webhooks update your backend. Use both: redirect for UX, webhooks for data integrity.

Can I use checkout sessions in sandbox mode?

Yes. Use your sandbox API key (sk_sandbox_*) and the sandbox base URL. The checkout URL will point to the sandbox frontend (e.g., https://sandbox.dhmad.tn/checkout/:sessionId).

What about instant escrows (digital goods)?

For escrows created with mode: "instant" (e.g. in-app coins, AI credits), use only the accept_pay action. The buyer pays in one step; there is no contract to sign and no “complete” step. After the buyer pays, you receive a webhook, deliver the goods, then call the Deliver Escrow API—the escrow auto-completes and funds are released. See Escrow Modes — Instant in the Escrows overview.

Can the buyer release funds without the seller delivering?

Yes, for standard and quick escrows. After payment (paid), the buyer can use a complete checkout session to release funds without the seller calling Deliver Escrow. Deliver is supplementary — it records that the seller marked delivery, but it is not a prerequisite for complete. Exception: Quick Escrow with Proof blocks early release from paid until the buyer accepts the seller’s purchase proof (409 PROOF_NOT_ACCEPTED).

What about quick escrows (products without a contract)?

For escrows created with mode: "quick" (e.g. physical or digital products between two users), use accept_pay then complete to release funds. The seller may call Deliver Escrow first, but deliver is optional — if the buyer is already satisfied after payment, send them a complete checkout session while the escrow is still paid. There is no contract to sign — sign_contract returns 400. See Escrow Modes — Quick in the Escrows overview.

What about Quick Escrow with Proof?

When you set fulfillmentPolicy.type: "purchase_proof_required" at creation, add submit_proof (seller uploads after pay) and review_proof (buyer accepts or requests corrections) before the seller can deliver. Fetch proof files with GET /escrows/:id/proof. See the Quick Escrow with Proof guide.

How do paid or delivered cancellation checkout sessions work?

After payment, buyers cannot cancel unilaterally. Use checkout sessions as follows:
  1. request_cancellation — Target the buyer’s email. Escrow must be paid or delivered.
  2. cancel_escrow — Target the seller’s email. Escrow must be paid or delivered. Cancels immediately and refunds the buyer when applicable.
  3. accept_cancellation or reject_cancellation — After the buyer requests, target the seller’s email so they can accept (refund) or reject.
Do not mix actions: targeting the seller for request_cancellation or the buyer for cancel_escrow returns 400. For escrows still in pending status (before payment), use Cancel Escrow instead — both parties can cancel via the API.

How do add funds and refund funds checkout sessions work?

While an escrow is paid or delivered (and was not funded via guest checkout), parties can adjust the held amount via hosted checkout:
  1. add_funds — Target the buyer’s email. The buyer adds more money from their DHMAD wallet into the escrow. Fees apply to the added amount only. DHMAD requires action step-up (email OTP) on the checkout page.
  2. refund_funds — Target the seller’s email. The seller refunds part or all of the remaining ledger balance to the buyer’s wallet. Refunding the full remaining balance cancels the escrow. Also requires action step-up.
Do not mix actions: targeting the seller for add_funds or the buyer for refund_funds returns 400. Subscribe to escrow.funds.added and escrow.funds.refunded webhooks to update your backend. A full refund that cancels the escrow also emits escrow.status.updated (to cancelled); the refund webhook payload includes adjustment.cancelled: true in that case. Guest-checkout escrows and open disputes / pending cancellation requests cannot use these actions (GUEST_ESCROW_ADJUSTMENTS_NOT_SUPPORTED, DISPUTE_OPEN, CANCELLATION_PENDING).
For API reference details, see Create Checkout Session and Get Checkout Session.