Documentation
Technical ReferenceREST API

Checkout metadata

Create-time checkout context, validation boundaries, response fields, and immutable read surfaces.

Checkout metadata

Use checkoutMetadata to attach non-sensitive merchant context to a checkout when you create it. FloPay copies the map into later billing records so you can correlate API reads and webhook events with your own order or workflow.

checkoutMetadata is separate from every existing metadata field. Product, provider, transaction, subscription, and legacy checkout metadata keep their current meanings and shapes.

Checkout metadata is created once. There is no public operation to edit it after session creation.

DTO contract

SurfaceFieldContract
CreateCheckoutSessionBodyDtocheckoutMetadataOptional request field: Record<string, string> | null.
CheckoutSessionResponseDtodata.checkoutMetadataOptional response field: Record<string, string>. It is inside data and is never null.
CheckoutSessionRecordDtocheckoutMetadataOptional response field on a stored checkout-session record: Record<string, string>. It is never null.
PaymentDtocheckoutMetadataOptional response field on the raw payment DTO: Record<string, string>. It is never null.
UserSubscriptionDtocheckoutMetadataOptional response field on the raw subscription DTO: Record<string, string>. It is never null.

The request type is Record<string, string> | null and the field is optional. The response type is Record<string, string>; it is optional and never null.

The request field is nullable because explicit null has the same stored result as omission. Response and read DTOs are optional and non-null: when no snapshot exists, the key is absent.

Create a checkout session

POST /v1/checkouts/sessions accepts the optional request field on CreateCheckoutSessionBodyDto.

POST /v1/checkouts/sessions
Content-Type: application/json
Idempotency-Key: checkout-order-123
{
  "clientId": "00000000-0000-0000-0000-000000000000",
  "currency": "USD",
  "products": [{ "code": "starter", "quantity": 1 }],
  "accountData": {
    "userId": "buyer-123",
    "email": "buyer@example.com"
  },
  "successUrl": "https://merchant.example/success",
  "cancelUrl": "https://merchant.example/checkout",
  "checkoutMetadata": {
    "merchantOrderId": "order-123"
  }
}

A 201 Created response is a CheckoutSessionResponseDto. The snapshot is at data.checkoutMetadata, not at the response root:

{
  "data": {
    "uuid": "c01a59cf-a07d-4f2a-9afd-09630cd4ea94",
    "nonce": "checkout-session-token-from-response",
    "checkoutMetadata": {
      "merchantOrderId": "order-123"
    }
  }
}

Omission, null, and an empty map

Create requestStored and returned behavior
Field omittedcheckoutMetadata is absent from reads and webhooks.
"checkoutMetadata": nullSame as omission. The response key is absent, not null.
"checkoutMetadata": {}The empty snapshot is preserved and returned as {}.
A populated mapThe string map is copied and returned on relevant snapshots.

Omitted input and null both leave the response field absent. An explicit {} remains {}.

Omitted and explicit null values also preserve the legacy checkout-create idempotency fingerprint. A populated map or {} participates in the fingerprint using canonical key ordering. This means an Idempotency-Key replay with the same pairs in a different object-key order is the same request, while a material metadata change is a different request and conflicts with the original key.

Validation

The backend is the runtime authority for validation. A value is valid only when all of these boundaries are satisfied:

BoundaryLimit
EntriesAt most 50 own string key-value pairs.
Key lengthAt most 40 Unicode code points per key.
Value lengthAt most 500 Unicode code points per value.
Serialized sizeAt most 8,192 UTF-8 bytes for the complete JSON.stringify representation, including braces, separators, quotes, and escaping.

Values exactly at every limit are accepted. The map must be a plain, non-array object and every value must be a string. Empty keys and empty values are valid. U+0000 and unmatched UTF-16 surrogates are rejected in both keys and values.

An invalid shape or an exceeded boundary returns 400 Bad Request. The standard validation response includes the merged validator message:

{
  "message": [
    "checkoutMetadata must contain at most 50 string pairs; keys and values must not contain U+0000 or unmatched UTF-16 surrogates; keys may contain up to 40 Unicode characters, values up to 500 Unicode characters, and the serialized size may be up to 8192 bytes"
  ],
  "error": "Bad Request",
  "statusCode": 400
}

SDK callers receive the same backend message through a FloPayError with type: "api_error", the HTTP status available as statusCode, and the backend error code when one is present. The SDK does not apply a second set of client-side metadata limits.

Read the snapshot

Checkout session

GET /v1/checkouts/sessions/{id} returns CheckoutSessionResponseDto, so read data.checkoutMetadata. The checkout-session token remains required for this checkout-page read.

Client-authenticated stored checkout-session reads use CheckoutSessionRecordDto. The list response contains records inside its existing pagination envelope, and the stored-record by-id response returns the raw DTO. In both cases, checkoutMetadata is an optional field on each record.

Payment

GET /v1/payments/{id} returns PaymentDto as the raw DTO, not wrapped in data:

{
  "id": "3c54b6ac-7ad5-4e56-9c2c-5a80c2ef40d0",
  "status": "succeeded",
  "checkoutMetadata": {
    "merchantOrderId": "order-123"
  }
}

The existing payment metadata: Record<string, unknown> | null field is unrelated and remains unchanged.

Subscription

GET /v1/subscriptions/{id} returns UserSubscriptionDto as the raw DTO, not wrapped in data:

{
  "uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
  "state": "active",
  "checkoutMetadata": {
    "merchantOrderId": "order-123"
  }
}

The existing subscription metadata: Record<string, unknown> | null field is unrelated and remains unchanged.

On this page