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

# Custom gateways

> Connect a store-owned payment gateway to Gamecart checkout

Custom gateways let a store route checkout to a seller-owned payment provider. Gamecart creates the order, calls your payment creation endpoint, redirects the buyer to your `paymentUrl`, and waits for your signed callback.

This guide documents the current contract implemented by Gamecart.

## Flow

<Steps>
  <Step title="Configure the gateway">
    Add a custom gateway in the dashboard with a public HTTPS payment creation URL. Gamecart generates a shared secret.
  </Step>

  <Step title="Buyer selects the gateway">
    The storefront sends checkout with `gatewayType: "CUSTOM"` and the selected `customGatewayId`.
  </Step>

  <Step title="Gamecart creates a payment">
    Gamecart persists a pending order, then sends a signed `payment.create` request to your payment creation URL.
  </Step>

  <Step title="You return a payment URL">
    Your service returns a public HTTPS `paymentUrl`. Gamecart redirects the buyer there.
  </Step>

  <Step title="You confirm the result">
    After payment, your service sends a signed callback to the Gamecart `callbackUrl` from the create-payment payload.
  </Step>
</Steps>

## Configure a custom gateway in the dashboard

<Steps>
  <Step title="Open payment settings">
    In the dashboard, select your store and open **Settings**. Then open **Gateways**.
  </Step>

  <Step title="Create a custom gateway">
    In the custom gateways area, click **Create custom gateway**.
  </Step>

  <Step title="Fill in display details">
    Add the gateway name, optional description, sort order, and optional icon URL. These values help buyers recognize the payment method at checkout.
  </Step>

  <Step title="Enter the payment creation URL">
    Paste the public HTTPS URL where your service receives Gamecart payment creation requests.
  </Step>

  <Step title="Choose the timeout and enabled state">
    Pick a request timeout between `1000` and `30000` milliseconds. Keep the gateway disabled until your endpoint is ready.
  </Step>

  <Step title="Save and copy the shared secret">
    After saving, copy the shared secret and store it securely. Gamecart shows it only once.
  </Step>

  <Step title="Enable and test checkout">
    Enable the gateway, place a controlled checkout order, verify the signed `payment.create` request, and send a callback for the test order.
  </Step>
</Steps>

Configuration fields:

| Field                | Rule                                               |
| -------------------- | -------------------------------------------------- |
| Name                 | Required, max `120` characters.                    |
| Description          | Optional, max `500` characters.                    |
| Payment creation URL | Required, max `2048` characters, public HTTPS URL. |
| Request timeout      | Between `1000` and `30000` milliseconds.           |
| Icon URL             | Optional HTTPS URL.                                |

## Storefront checkout request

When a buyer selects a custom gateway, checkout uses the public storefront checkout endpoint:

```http theme={"dark"}
POST /v1/stores/checkout
Content-Type: application/json
```

Relevant request fields:

```json theme={"dark"}
{
  "gatewayType": "CUSTOM",
  "customGatewayId": "3f173d60-2174-4e56-94ff-4fa3a4fd20a4",
  "email": "alex@example.com",
  "name": "Alex Player",
  "accountIdentifierValue": "AlexInGame",
  "successUrl": "https://store.example.com/success",
  "cancelUrl": "https://store.example.com/cancel",
  "cart": {
    "items": []
  }
}
```

Rules:

* `gatewayType: "CUSTOM"` requires `customGatewayId`.
* Native gateways reject `customGatewayId`.
* The custom gateway must belong to the store, be enabled, not deleted, allowed by plan, and have a safe public payment creation URL.
* Gamecart revalidates the cart and totals before it calls your gateway.

Checkout response:

```json theme={"dark"}
{
  "orderId": "GC-7K9Q2X",
  "redirectUrl": "https://payments.example.com/pay/abc",
  "externalPaymentId": "pay_abc",
  "gatewayType": "CUSTOM",
  "customGatewayId": "3f173d60-2174-4e56-94ff-4fa3a4fd20a4"
}
```

## Gamecart to your payment creation URL

Gamecart sends a signed request:

```http theme={"dark"}
POST https://payments.example.com/gamecart/create-payment
Content-Type: application/json
Accept: application/json
Gamecart-Signature: t=1782565500,v1=<hex_hmac_sha256>
Gamecart-Timestamp: 1782565500
Gamecart-Event-Id: payment.create:GC-7K9Q2X
Gamecart-Event-Type: payment.create
```

Request body:

```json theme={"dark"}
{
  "orderId": "GC-7K9Q2X",
  "storeId": "7e4d76f4-55f9-4fbf-b0da-43fb1b13a914",
  "customGatewayId": "3f173d60-2174-4e56-94ff-4fa3a4fd20a4",
  "amount": 19.99,
  "currency": "USD",
  "items": [
    {
      "productId": 101,
      "name": "VIP Rank",
      "quantity": 1,
      "unitPrice": 19.99
    }
  ],
  "buyer": {
    "name": "Alex Player",
    "email": "alex@example.com",
    "accountIdentifierValue": "AlexInGame"
  },
  "successUrl": "https://store.example.com/orders/GC-7K9Q2X",
  "cancelUrl": "https://store.example.com/orders/GC-7K9Q2X",
  "orderUrl": "https://store.example.com/payment/GC-7K9Q2X",
  "callbackUrl": "https://api.gamecart.gg/v1/gateway/custom/7e4d76f4-55f9-4fbf-b0da-43fb1b13a914/3f173d60-2174-4e56-94ff-4fa3a4fd20a4/GC-7K9Q2X"
}
```

Verify `Gamecart-Signature` with the shared secret before creating a payment. See [Webhook signatures](/webhooks/signatures) for the canonical string.

Your response must be `2xx` JSON:

```json theme={"dark"}
{
  "paymentUrl": "https://payments.example.com/pay/abc",
  "externalPaymentId": "pay_abc"
}
```

Rules:

* `paymentUrl` is required.
* `paymentUrl` must be an absolute public HTTPS URL.
* `externalPaymentId` is optional and must be at most `255` characters.
* Gamecart does not follow redirects when calling your payment creation URL.
* If payment creation fails, Gamecart cancels the pending order and releases coupon reservations.

## Your callback to Gamecart

Send the result to the exact `callbackUrl` from the create-payment request:

```http theme={"dark"}
POST /v1/gateway/custom/{storeId}/{customGatewayId}/{orderId}
Content-Type: application/json
Gamecart-Signature: t=1782565800,v1=<hex_hmac_sha256>
Gamecart-Event-Id: pay_abc:approved
```

Callback body:

```json theme={"dark"}
{
  "eventId": "pay_abc:approved",
  "status": "APPROVED",
  "amount": 19.99,
  "currency": "USD",
  "occurredAt": "2026-06-27T13:10:00",
  "externalPaymentId": "pay_abc"
}
```

Rules:

* `eventId` in the body must match `Gamecart-Event-Id`.
* `status` must not be `PENDING`.
* Allowed status values are `APPROVED`, `REJECTED`, `CANCELLED`, `REFUNDED`, and `DISPUTE`.
* `amount` must match the order total exactly.
* `currency` must match the order currency, case-insensitively.
* The order must belong to the store and custom gateway in the URL.
* Sign the raw JSON body with the shared secret and the same canonical payload format.

Gamecart returns `202 Accepted` when the callback is accepted. Duplicate successful callback `eventId` values for the same custom gateway are idempotent.
