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:
- Get shipping rates –
POST /v1/shipping/rateswith the customer’s address - Create checkout –
POST /v1/checkoutwith the cart, address, and selected shipping rate - Mount Stripe Elements – Use the returned
client_secretwith Stripe.js to render the Payment Element - Confirm payment – Call
stripe.confirmPayment()on the client side - Poll for completion –
GET /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:
- Looks up the checkout record by ID from the payment intent metadata
- Creates an order with line items from the cart
- Deducts inventory from each variant (rejects if insufficient stock)
- Marks the checkout as
completedand links theorder_id - Dispatches
order.createdandorder.paidwebhook events - Fires
inventory.lowfor 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 |