Hydra is now in beta|Get started free|Follow our journey on X.com

Checkout API
On this page

Checkout

Checkout converts a cart into a payment intent. The storefront collects payment using Stripe Elements with the returned client_secret. When payment succeeds, a webhook automatically creates an order, deducts inventory, and marks the checkout as completed. The checkout flow is designed for storefront use with publishable keys.

Base URL: https://api.hydrajs.dev

Auth: Checkout endpoints accept publishable keys (pk_live_* or pk_test_*) for client-side storefront use. The GET endpoint also accepts checkout-scoped tokens – pass the checkout ID itself as a Bearer token to retrieve that checkout without an API key.

Endpoints

Method Path Auth Description
POST /v1/checkout Publishable Create a checkout
GET /v1/checkout/{id} Publishable / Checkout token Get checkout status

Create a checkout

POST /v1/checkout

Creates a checkout for a cart. Validates line items, resolves the selected shipping rate, applies multi-currency conversion, and creates a payment intent. Returns a client_secret for completing payment via Stripe Elements, plus a display payload with structured order summary data for rendering a checkout UI. The checkout expires after 30 minutes.

Request body

Field Type Required Description
cart_id string Yes Cart ID to check out
customer_email string Conditional Customer email. Required if customer_id is not provided
customer_id string Conditional Existing customer ID. Required if customer_email is not provided
shipping_address object Conditional Shipping address. Required for carts with physical products
shipping_address.first_name string No First name (max 100 chars)
shipping_address.last_name string No Last name (max 100 chars)
shipping_address.line1 string Yes Street address (max 255 chars)
shipping_address.line2 string No Apartment, suite, etc. (max 255 chars)
shipping_address.city string Yes City (max 100 chars)
shipping_address.state string Yes State or province (max 100 chars)
shipping_address.postal_code string Yes Postal or ZIP code (max 20 chars)
shipping_address.country string Yes 2-letter ISO country code (e.g. US)
billing_address object No Billing address (same schema as shipping)
shipping_rate_id string No Selected shipping rate ID from POST /v1/shipping/rates
currency string No 3-letter ISO currency code for multi-currency checkout

Customer identification

You must provide either customer_id or customer_email (or both). If customer_id is provided, the customer record is looked up and its email is used as a fallback when customer_email is omitted.

Shipping rate selection

Use POST /v1/shipping/rates to get available rates for the customer’s address, then pass the chosen rate’s id as shipping_rate_id. If omitted, shipping cost is $0 (suitable for digital products or free shipping).

Duplicate detection

If a pending, non-expired checkout already exists for the same cart and currency, the existing checkout is returned instead of creating a new one. This prevents duplicate charges when the customer refreshes or retries.

Request

curl -X POST https://api.hydrajs.dev/v1/checkout \
  -H "Authorization: Bearer pk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cart_id": "cart_abc123",
    "customer_email": "jane@example.com",
    "shipping_rate_id": "shr_abc123",
    "shipping_address": {
      "first_name": "Jane",
      "last_name": "Doe",
      "line1": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "postal_code": "78701",
      "country": "US"
    }
  }'

Response 201

{
	"data": {
		"id": "chk_abc123",
		"status": "pending",
		"currency": "USD",
		"client_secret": "pi_abc123_secret_xyz",
		"requires_shipping": true,
		"subtotal": 5998,
		"shipping": 599,
		"tax": 435,
		"tax_summary": {
			"total_tax": 435,
			"tax_inclusive": false,
			"tax_lines": [
				{
					"name": "TX State Tax",
					"rate": 6.25,
					"amount": 375,
					"type": "line_item"
				},
				{
					"name": "TX Shipping Tax",
					"rate": 6.25,
					"amount": 37,
					"type": "shipping"
				},
				{
					"name": "Austin City Tax",
					"rate": 1,
					"amount": 23,
					"type": "line_item"
				}
			]
		},
		"total": 7032,
		"publishable_key": "pk_live_xxx",
		"store_name": "Acme Store",
		"mode": "payment",
		"display": {
			"title": "Order summary",
			"items": [
				{
					"label": "Classic T-Shirt \u2014 Black / M",
					"image": "https://cdn.hydrajs.dev/img.jpg",
					"detail": "Qty: 2",
					"amount": 5998
				}
			],
			"lines": [
				{ "label": "Subtotal", "amount": 5998 },
				{ "label": "Shipping", "amount": 599 },
				{ "label": "Tax", "amount": 435 },
				{ "label": "Total", "amount": 7032, "bold": true }
			],
			"pay_label": "Pay",
			"confirm_message": "Your order has been confirmed."
		},
		"expires_at": "2026-08-17T10:30:00.000Z",
		"created_at": "2026-08-17T10:00:00.000Z"
	}
}

Tax calculation

Tax is calculated automatically when your project has tax rates configured (Settings > Tax) and the checkout includes a shipping address. Tax-exclusive projects add tax on top of the subtotal. Tax-inclusive projects include tax in the listed prices – the tax_summary shows the back-calculated tax for records, but total is unchanged. When no tax rates match the address, tax is 0 and tax_summary is null.

Multi-currency example

curl -X POST https://api.hydrajs.dev/v1/checkout \
  -H "Authorization: Bearer pk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cart_id": "cart_abc123",
    "customer_email": "hans@example.de",
    "currency": "EUR",
    "shipping_rate_id": "shr_def456",
    "shipping_address": {
      "first_name": "Hans",
      "last_name": "Mueller",
      "line1": "Hauptstrasse 42",
      "city": "Berlin",
      "state": "Berlin",
      "postal_code": "10115",
      "country": "DE"
    }
  }'

Multi-currency

The currency must be in the project’s list of enabled currencies. Prices are converted using live exchange rates with the project’s configured margin and rounding. Variants with explicit per-currency price overrides use those values instead of conversion. The FX rate is locked for the duration of the checkout.

Error 400 – empty cart

{
	"error": {
		"code": "invalid_request",
		"message": "Cart is empty."
	}
}

Get checkout status

GET /v1/checkout/{id}

Returns the current status of a checkout. For pending checkouts, returns the client_secret so the storefront can re-mount Stripe Elements on page refresh. Includes a display payload for rendering the order summary. Use this to check if payment has completed and retrieve the order_id.

Auth

This endpoint supports two auth methods:

  • Publishable key: Authorization: Bearer pk_live_YOUR_KEY
  • Checkout-scoped token: Authorization: Bearer chk_abc123 – pass the checkout ID itself as a bearer token. Scoped to read-only access on that single checkout. Used by hosted checkout pages that don’t have an API key.

Query parameters

Parameter Type Description
fields string Comma-separated fields to return

Request

curl https://api.hydrajs.dev/v1/checkout/chk_abc123 \
  -H "Authorization: Bearer pk_live_YOUR_KEY"

Or with a checkout-scoped token:

curl https://api.hydrajs.dev/v1/checkout/chk_abc123 \
  -H "Authorization: Bearer chk_abc123"

Response 200 – pending

{
	"data": {
		"id": "chk_abc123",
		"cart_id": "cart_abc123",
		"order_id": null,
		"status": "pending",
		"currency": "USD",
		"base_currency": "USD",
		"exchange_rate": null,
		"requires_shipping": true,
		"shipping_rate_id": "shr_abc123",
		"shipping_cost": 599,
		"subtotal": 5998,
		"tax": 435,
		"tax_summary": {
			"total_tax": 435,
			"tax_inclusive": false,
			"tax_lines": [
				{ "name": "TX State Tax", "rate": 6.25, "amount": 375, "type": "line_item" },
				{ "name": "TX Shipping Tax", "rate": 6.25, "amount": 37, "type": "shipping" },
				{ "name": "Austin City Tax", "rate": 1, "amount": 23, "type": "line_item" }
			]
		},
		"total": 7032,
		"client_secret": "pi_abc123_secret_xyz",
		"customer_id": null,
		"customer_email": "jane@example.com",
		"publishable_key": "pk_live_xxx",
		"store_name": "Acme Store",
		"mode": "payment",
		"display": {
			"title": "Order summary",
			"items": [
				{
					"label": "Classic T-Shirt \u2014 Black / M",
					"image": "https://cdn.hydrajs.dev/img.jpg",
					"detail": "Qty: 2",
					"amount": 5998
				}
			],
			"lines": [
				{ "label": "Subtotal", "amount": 5998 },
				{ "label": "Shipping", "amount": 599 },
				{ "label": "Tax", "amount": 435 },
				{ "label": "Total", "amount": 7032, "bold": true }
			],
			"pay_label": "Pay",
			"confirm_message": "Your order has been confirmed."
		},
		"expires_at": "2026-08-17T10:30:00.000Z",
		"created_at": "2026-08-17T10:00:00.000Z"
	}
}

Response 200 – completed

{
	"data": {
		"id": "chk_abc123",
		"cart_id": "cart_abc123",
		"order_id": "ord_def456",
		"status": "completed",
		"currency": "USD",
		"base_currency": "USD",
		"exchange_rate": null,
		"requires_shipping": true,
		"shipping_rate_id": "shr_abc123",
		"shipping_cost": 599,
		"subtotal": 5998,
		"tax": 435,
		"tax_summary": {
			"total_tax": 435,
			"tax_inclusive": false,
			"tax_lines": [
				{ "name": "TX State Tax", "rate": 6.25, "amount": 375, "type": "line_item" },
				{ "name": "TX Shipping Tax", "rate": 6.25, "amount": 37, "type": "shipping" },
				{ "name": "Austin City Tax", "rate": 1, "amount": 23, "type": "line_item" }
			]
		},
		"total": 7032,
		"client_secret": null,
		"customer_id": null,
		"customer_email": "jane@example.com",
		"publishable_key": "pk_live_xxx",
		"store_name": "Acme Store",
		"mode": "payment",
		"display": {
			"title": "Order summary",
			"items": [
				{
					"label": "Classic T-Shirt \u2014 Black / M",
					"image": "https://cdn.hydrajs.dev/img.jpg",
					"detail": "Qty: 2",
					"amount": 5998
				}
			],
			"lines": [
				{ "label": "Subtotal", "amount": 5998 },
				{ "label": "Shipping", "amount": 599 },
				{ "label": "Tax", "amount": 435 },
				{ "label": "Total", "amount": 7032, "bold": true }
			],
			"pay_label": "Pay",
			"confirm_message": "Your order has been confirmed."
		},
		"expires_at": "2026-08-17T10:30:00.000Z",
		"created_at": "2026-08-17T10:00:00.000Z"
	}
}

Order creation

When payment succeeds, Hydra automatically creates an order from the cart contents, deducts inventory, dispatches order.created and order.paid webhook events, and sets the checkout status to completed. The order_id field links to the created order.


Payment flow

Hydra uses Stripe Elements for payment collection. The typical integration flow:

  1. Get shipping ratesPOST /v1/shipping/rates with the customer’s address
  2. Create checkoutPOST /v1/checkout with the cart, address, and selected shipping rate
  3. Mount Stripe Elements – Use the returned client_secret with Stripe.js to render the Payment Element
  4. Confirm payment – Call stripe.confirmPayment() on the client side
  5. Poll for completionGET /v1/checkout/{id} to check when the order is created

Webhook

Hydra exposes an internal endpoint at POST /webhooks/stripe that handles Stripe webhook events. This is not part of the public API – it is configured directly in your Stripe dashboard.

When Stripe sends a payment_intent.succeeded event, Hydra:

  1. Looks up the checkout record by ID from the payment intent metadata
  2. Creates an order with line items from the cart
  3. Deducts inventory from each variant (rejects if insufficient stock)
  4. Marks the checkout as completed and links the order_id
  5. Dispatches order.created and order.paid webhook events
  6. Fires inventory.low for any variant that falls below its threshold

Webhook configuration

Configure the Stripe webhook endpoint in your Stripe dashboard to send payment_intent.succeeded events to https://api.hydrajs.dev/webhooks/stripe. The STRIPE_WEBHOOK_SECRET must be set in your project environment.


The checkout object

Field Type Description
id string Unique ID (prefix: chk_)
cart_id string Cart that was checked out
order_id string | null Created order ID (set after payment completes)
status string pending or completed
currency string Checkout currency (may differ from base for multi-currency)
base_currency string Project’s base currency
exchange_rate number | null FX rate applied (set for multi-currency checkouts)
requires_shipping boolean Whether the checkout contains physical products
shipping_rate_id string | null Selected shipping rate ID
shipping_cost number | null Shipping cost in checkout currency (cents)
subtotal number | null Line item subtotal in checkout currency (cents)
tax number Total tax in cents (0 when no tax applies)
tax_summary object | null Tax breakdown with per-jurisdiction lines (see below)
total number | null Total charge amount in checkout currency (cents). Includes tax for tax-exclusive projects
client_secret string | null Payment intent client secret (set for pending checkouts, null for completed)
customer_id string | null Linked customer ID
customer_email string Customer email address
publishable_key string Payment provider publishable key for client-side SDK initialization
store_name string Project name (for checkout page header display)
mode string Payment mode: payment (one-off) or setup (trial/subscription)
display object Structured order summary for rendering checkout UI (see below)
shipping number Shipping cost in checkout currency (returned on create only)
expires_at string ISO 8601 timestamp (30 minutes after creation)
created_at string ISO 8601 timestamp

The display object

The display payload provides all the data needed to render an order summary UI. The frontend renders whatever display contains without interpreting the checkout type.

Field Type Description
title string Section heading (e.g. “Order summary”)
items array Line items to display
items[].label string Item description (e.g. “Classic T-Shirt – Black / M”)
items[].image string | null Thumbnail image URL
items[].detail string Quantity or detail text (e.g. “Qty: 2”)
items[].amount number Line total in cents
lines array Summary lines (subtotal, shipping, total)
lines[].label string Line label
lines[].amount number Amount in cents
lines[].bold boolean Whether to render the line with emphasis (used for total)
pay_label string Pay button text (e.g. “Pay”)
confirm_message string Confirmation message shown after successful payment

The tax_summary object

Returned when tax rates match the checkout’s shipping address. null when no tax applies.

Field Type Description
total_tax number Total tax amount in cents
tax_inclusive boolean Whether prices already include tax
tax_lines array Per-jurisdiction tax breakdown
tax_lines[].name string Tax name (e.g. “CA State Tax”)
tax_lines[].rate number Tax rate percentage (e.g. 7.25)
tax_lines[].amount number Tax amount in cents for this jurisdiction
tax_lines[].type string line_item or shipping