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

# Complete Checkout Session

> Charge the delegated payment token and issue tickets

# Complete Checkout Session

Charges the payment token and issues tickets. This is the only endpoint that moves money.

<Note>
  Requires `checkout: write`, an allowlisted partner key, and a **signed request**. Signature verification matters most here: it runs over the raw body before parsing, so a tampered amount or a swapped token cannot reach the payment logic.
</Note>

The session must be `ready_for_payment`. Completing one that is not returns `409 invalid_session_state`.

## Path Parameters

<ParamField path="checkout_session_id" type="string" required>
  The session to pay.
</ParamField>

## Body

<ParamField body="payment_data" type="object" required>
  <Expandable title="Payment data">
    <ParamField body="token" type="string" required>
      A **Stripe Shared Payment Token**. An opaque, single-use handle minted by your payment provider.
    </ParamField>

    <ParamField body="provider" type="string" required>
      Must be `stripe`.
    </ParamField>

    <ParamField body="billing_address" type="object">
      Optional billing address.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="buyer" type="object">
  A final chance to supply or correct the buyer before the ticket is issued.
</ParamField>

<Warning>
  **`payment_data` accepts a token and nothing else resembling a card.** The object is validated strictly: a key like `card` or `card_number` is **rejected with `400`**, not ignored.

  Gomry has no raw-PAN code path. Accepting cardholder data here would move the platform from PCI SAQ-A to SAQ-D, and a card number reaching our logs and request traces is the one failure that cannot be walked back. If your agent holds a raw card number rather than a token, it cannot complete a Gomry checkout — send the buyer to the event `url` from the catalog instead.
</Warning>

## Response

`200` with the [session](/api-reference/checkout-sessions/get-checkout-session#response), now `status: "completed"` and carrying an `order`.

Surface `order.permalink_url` to your buyer. It opens without a Gomry login and is the same link the confirmation email sends.

<Warning>
  **`completed` means tickets exist.** We never report `completed` on a succeeded payment that failed to produce tickets — that would be money taken without a product. If you get `completed`, the tickets are issued.
</Warning>

## Completion is idempotent

Completing an already-completed session **returns the existing order** rather than charging again, and concurrent `complete` calls on one session cannot both charge.

Always send an `Idempotency-Key`, and reuse the same one when retrying. If a response is lost in transit, retrying with the same key is safe.

## Approval-required tickets

When a tier has `requires_approval: true`, a successful completion produces a **pending request, not admission**. The session reports `completed` and the payment is taken, but the organizer must still approve.

Check `requires_approval` on the [catalog detail](/api-reference/catalog/get-catalog-event) before selling, and tell your buyer. An agent that reports "you're going" on an approval-required ticket has told the buyer something untrue.

## Failures

| Code                      | Status | What to do                                                                                     |
| ------------------------- | ------ | ---------------------------------------------------------------------------------------------- |
| `payment_declined`        | `402`  | Do not retry the same token. Ask for another payment method.                                   |
| `payment_requires_action` | `402`  | The payment needs 3DS. Surface this to your buyer.                                             |
| `amount_mismatch`         | `409`  | The total changed since you quoted it. Re-read the session, confirm the new total, then retry. |
| `sold_out`                | `409`  | Inventory went while the session was open.                                                     |
| `invalid_session_state`   | `409`  | Not `ready_for_payment` — read the session.                                                    |

<Warning>
  `amount_mismatch` exists so a buyer is never charged a number they did not agree to. Never retry it blindly with the old total; re-read the session and re-confirm.
</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST \
    "https://www.gomry.com/api/v1/checkout_sessions/acp_sess_AbCdEfGhIjKlMnOpQrSt/complete" \
    -H "X-API-KEY: your_api_key" \
    -H "Content-Type: application/json" \
    -H "API-Version: 2025-09-12" \
    -H "Timestamp: 2026-09-10T12:02:00Z" \
    -H "Signature: base64-hmac-sha256" \
    -H "Idempotency-Key: 8f14e45f-ea0c-4b9f-9c2a-1d3e5f7a9b0c" \
    -d '{
      "payment_data": {
        "token": "spt_1AbCdEfGhIjKlMnOpQrSt",
        "provider": "stripe"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const body = JSON.stringify({
    payment_data: { token: sharedPaymentToken, provider: "stripe" },
  });
  const timestamp = new Date().toISOString();
  const signature = crypto
    .createHmac("sha256", process.env.GOMRY_ACP_SIGNING_SECRET)
    .update(`${timestamp}.${body}`)
    .digest("base64");

  const res = await fetch(
    `https://www.gomry.com/api/v1/checkout_sessions/${sessionId}/complete`,
    {
      method: "POST",
      headers: {
        "X-API-KEY": process.env.GOMRY_API_KEY,
        "Content-Type": "application/json",
        "API-Version": "2025-09-12",
        Timestamp: timestamp,
        Signature: signature,
        "Idempotency-Key": idempotencyKey, // reuse on retry
      },
      body,
    }
  );

  const session = await res.json();
  if (session.status === "completed") {
    showBuyer(session.order.permalink_url);
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "id": "acp_sess_AbCdEfGhIjKlMnOpQrSt",
    "buyer": { "name": "Ada Lovelace", "email": "ada@example.com" },
    "status": "completed",
    "currency": "usd",
    "line_items": [
      {
        "id": "li_1",
        "item": { "id": "TktAbCdEfGhIjKlMnOpQ", "quantity": 2 },
        "base_amount": 15800,
        "discount": 0,
        "subtotal": 15800,
        "tax": 0,
        "total": 16790
      }
    ],
    "totals": [
      { "type": "items_base_amount", "display_text": "Tickets", "amount": 15800 },
      { "type": "fee", "display_text": "Service fee", "amount": 990 },
      { "type": "total", "display_text": "Total", "amount": 16790 }
    ],
    "messages": [],
    "order": {
      "id": "ord_AbCdEfGhIjKlMnOpQrSt",
      "checkout_session_id": "acp_sess_AbCdEfGhIjKlMnOpQrSt",
      "permalink_url": "https://www.gomry.com/ticket/AbCdEfGhIjKlMnOpQrSt?pk=pay_XyZ123"
    }
  }
  ```

  ```json 402 declined theme={null}
  {
    "type": "processing_error",
    "code": "payment_declined",
    "message": "The payment method was declined."
  }
  ```
</ResponseExample>
