> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dhmad.tn/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Checkout Session

> Create a checkout session for critical escrow actions

<Endpoint method="POST" path="/api/v1/escrows/:id/sessions" />

Create a checkout session for a critical escrow action. The session allows you to redirect a specific user to dhmad.tn to perform the action (e.g., sign contract, accept & pay, complete, dispute, or handle cancellation). The user is redirected back to your `redirectUrl` after completing or abandoning the flow.

The API validates that the action is still needed before creating a session. If the action is already done (e.g. the target user has already signed the contract, or payment is already completed), the request returns **400** with a structured error (`error.code`, `error.user_message`). Use these to show a clear message in your app instead of redirecting to checkout.

<Warning>
  Configure allowed redirect URLs in your [Developer Dashboard](https://developer.dhmad.tn/dashboard) settings before creating checkout sessions. If no redirect URLs are configured, or if the `redirectUrl` does not match any allowed URL, the request will fail with a 400 error.
</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://dhmad.tn/api/v1/escrows/507f1f77bcf86cd799439011/sessions \
    -H "Authorization: Bearer sk_live_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "action": "sign_contract",
      "targetUserEmail": "buyer@example.com",
      "redirectUrl": "https://myapp.com/checkout/callback",
      "metadata": {
        "order_id": "ord_123"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://dhmad.tn/api/v1/escrows/507f1f77bcf86cd799439011/sessions', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_live_your_api_key_here',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      action: 'sign_contract',
      targetUserEmail: 'buyer@example.com',
      redirectUrl: 'https://myapp.com/checkout/callback',
      metadata: { order_id: 'ord_123' }
    })
  });

  const data = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://dhmad.tn/api/v1/escrows/507f1f77bcf86cd799439011/sessions',
      headers={
          'Authorization': 'Bearer sk_live_your_api_key_here',
          'Content-Type': 'application/json'
      },
      json={
          'action': 'sign_contract',
          'targetUserEmail': 'buyer@example.com',
          'redirectUrl': 'https://myapp.com/checkout/callback',
          'metadata': {'order_id': 'ord_123'}
      }
  )

  data = response.json()
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "sessionId": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://dhmad.tn/checkout/550e8400-e29b-41d4-a716-446655440000",
    "expiresAt": "2024-01-15T10:30:00Z"
  }
  ```
</ResponseExample>

## Path Parameters

<ParamField path="id" type="string" required>
  Escrow ID
</ParamField>

## Request Body

<ParamField body="action" type="string" required>
  The action the user will perform. Must be one of: `sign_contract`, `accept_pay`, `complete`, `dispute`, `request_cancellation`, `reject_cancellation`, `accept_cancellation`, `cancel_escrow`, `submit_proof`, `review_proof`. If the action is already done (e.g. contract already signed, payment already made), the API returns 400 with a business rule error—see Error Responses below.

  **Cancellation actions (paid or delivered escrows):**

  * `request_cancellation` — **Buyer only.** Target the buyer's email. The buyer cannot cancel unilaterally after payment; they request cancellation and the seller must accept or reject.
  * `cancel_escrow` — **Seller only.** Target the seller's email. Cancels immediately and refunds the buyer when applicable.
  * `reject_cancellation` / `accept_cancellation` — Target the **seller's** email (counterparty of the buyer who requested). Create these only after a pending cancellation request exists.
  * Targeting the wrong party returns **400** (`CANCELLATION_REQUEST_BUYER_ONLY`, `CANCEL_ESCROW_SELLER_ONLY`, or `CHECKOUT_TARGET_NOT_BUYER` / `CHECKOUT_TARGET_NOT_SELLER`).

  **By escrow mode:**

  * **standard** — all actions apply when status allows (except proof actions).
  * **quick** — no contract; `sign_contract` returns 400 (`QUICK_ESCROW_NO_CONTRACT`). Use `accept_pay` then `complete`. With [Quick Escrow with Proof](/guides/quick-escrow-with-proof), use `submit_proof` (seller) and `review_proof` (buyer) while `proofInfo.phase` requires them.
  * **instant** — only `accept_pay` (and dispute/cancellation actions) are valid; `sign_contract`, `complete`, and proof actions return 400.
</ParamField>

<ParamField body="targetUserEmail" type="string" required>
  Email of the user who will perform the action. Only this user can complete the session.
</ParamField>

<ParamField body="redirectUrl" type="string" required>
  URL to redirect the user after they complete or abandon the flow. Must match one of your allowed redirect URLs configured in the developer dashboard. Must use HTTPS (except `http://localhost` for development).
</ParamField>

<ParamField body="metadata" type="object" required={false}>
  Optional key-value pairs (strings only). Useful for tracking orders or internal references. Values must be 500 characters or less.
</ParamField>

## Response Fields

<ParamField response="sessionId" type="string">
  Unique session identifier. Use this to construct the checkout URL or to query session status.
</ParamField>

<ParamField response="url" type="string">
  Full URL to redirect the user to: `https://dhmad.tn/checkout/:sessionId` (or sandbox URL for sandbox mode).
</ParamField>

<ParamField response="expiresAt" type="string">
  ISO 8601 timestamp when the session expires. Sessions expire 30 minutes after creation.
</ParamField>

<Info title="Session creation and KYC">
  No KYC check is performed when creating a checkout session. You can create an `accept_pay` session regardless of the buyer's KYC status. If the buyer does not have approved KYC, they will be prompted to complete identity verification **inside** the DHMAD checkout flow (inline Verify KYC) before they can complete Accept & Pay. You do not receive a separate error code for KYC at session creation time.
</Info>

## Error Responses

When a request is rejected because of a business rule (e.g. the action is already done, or redirect URL is invalid), the API returns **400 Bad Request** with an `error` object. Use `error.code` to branch in your app and `error.user_message` to show a clear message to the end user; `error.message` is for developers and logging. The table below lists **all** error codes returned by this endpoint.

**Business rule error shape (400)**

```json theme={null}
{
  "error": {
    "code": "CONTRACT_ALREADY_SIGNED",
    "type": "business_rule_violation",
    "message": "Cannot create checkout session: this signer has already signed the contract.",
    "user_message": "This contract has already been signed."
  }
}
```

| Code                                  | When                                                                                                    |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `NO_REDIRECT_URLS_CONFIGURED`         | Developer has no allowed redirect URLs in dashboard settings.                                           |
| `REDIRECT_URL_NOT_ALLOWED`            | `redirectUrl` does not match any allowed redirect URL.                                                  |
| `REDIRECT_URL_MUST_BE_HTTPS`          | `redirectUrl` is not HTTPS (except `http://localhost`).                                                 |
| `INSTANT_ESCROW_NO_CONTRACT`          | Action is `sign_contract` but escrow is instant (no contract). Use `accept_pay` instead.                |
| `INSTANT_ESCROW_NO_COMPLETE_STEP`     | Action is `complete` but escrow is instant; completion happens via deliver endpoint.                    |
| `QUICK_ESCROW_NO_CONTRACT`            | Action is `sign_contract` but escrow is quick (no contract). Use `accept_pay` instead.                  |
| `CONTRACT_ALREADY_SIGNED`             | Action is `sign_contract` and the target user has already signed the contract.                          |
| `PAYMENT_ALREADY_DONE`                | Action is `accept_pay` but the escrow is already paid, delivered, or completed.                         |
| `ESCROW_ALREADY_COMPLETED`            | Action is `complete` but the escrow is already completed.                                               |
| `DISPUTE_ALREADY_OPENED`              | Action is `dispute` but a dispute has already been opened.                                              |
| `CANCELLATION_REQUEST_INVALID_STATUS` | Action is `request_cancellation` but escrow is not `paid` or `delivered`.                               |
| `CANCELLATION_REQUEST_BUYER_ONLY`     | Action is `request_cancellation` but `targetUserEmail` is the seller. Use `cancel_escrow` instead.      |
| `CANCEL_ESCROW_INVALID_STATUS`        | Action is `cancel_escrow` but escrow is not `paid` or `delivered`.                                      |
| `CANCEL_ESCROW_SELLER_ONLY`           | Action is `cancel_escrow` but `targetUserEmail` is the buyer. Use `request_cancellation` instead.       |
| `CHECKOUT_TARGET_NOT_BUYER`           | Action is `request_cancellation` (or another buyer-only action) but `targetUserEmail` is not the buyer. |
| `CHECKOUT_TARGET_NOT_SELLER`          | Action is `cancel_escrow` (or another seller-only action) but `targetUserEmail` is not the seller.      |
| `CANCELLATION_ALREADY_REQUESTED`      | Action is `request_cancellation` but a cancellation has already been requested or processed.            |
| `NO_PENDING_CANCELLATION`             | Action is `reject_cancellation` or `accept_cancellation` but there is no pending cancellation request.  |

**Example: contract already signed**

```json theme={null}
{
  "error": {
    "code": "CONTRACT_ALREADY_SIGNED",
    "type": "business_rule_violation",
    "message": "Cannot create checkout session: this signer has already signed the contract.",
    "user_message": "This contract has already been signed."
  }
}
```

**Example: payment already done**

```json theme={null}
{
  "error": {
    "code": "PAYMENT_ALREADY_DONE",
    "type": "business_rule_violation",
    "message": "Cannot create checkout session: payment has already been made for this escrow.",
    "user_message": "Payment has already been completed for this transaction."
  }
}
```

Handle these in your app by checking `response.error?.code` and displaying `response.error?.user_message` to the user instead of redirecting to checkout.

### 401 Unauthorized

```json theme={null}
{
  "error": "Unauthorized"
}
```

### 403 Forbidden

```json theme={null}
{
  "error": "Forbidden",
  "message": "You do not have access to this escrow."
}
```

### 404 Not Found

**Escrow Not Found**

```json theme={null}
{
  "error": "Escrow not found"
}
```

**Developer Not Found**

```json theme={null}
{
  "error": "Developer not found"
}
```

***

<Note>
  Sessions expire 30 minutes after creation. Ensure users complete the flow within this window, or create a new session if needed.
</Note>

<Info title="Instant escrows">
  For escrows created with `mode: "instant"`, only the `accept_pay` action (and dispute/cancellation actions) is valid. Do not use `sign_contract` or `complete`; they will return 400. After the buyer pays, deliver the digital goods and call [Deliver Escrow](/api-reference/escrows/deliver-escrow) to auto-complete and release funds.
</Info>
