Add shipping to a checkout
A flat shipping rate is one price for every order, such as $9 to anywhere in the US. You set it up once as a delivery method, and Flint's hosted checkout offers it to every buyer whose order has something to ship. The checkout page collects the buyer's shipping address, adds the shipping charge and the tax on it to the total, and saves the address on the order for you to ship to.
For rates that change by region, free shipping over a threshold, in-store pickup, or local delivery, see Delivery options. To price shipping from a carrier API on your own server, see Live delivery rates with a callback.
Before you start#
You need:
- A test API key with
commerce.delivery.write,settings.write, andcheckouts.checkout_sessions.write. Reading the order after payment needscommerce.orders.read. - An active Location to ship from. Locations you create through the API already have the address and time zone shipping needs.
- An order with physical items. Every physical product gets a delivery profile that says how it can be delivered. Unless you assign your own, Flint gives it the General profile, which allows shipping. Digital products, services, and gift cards never need shipping, so checkout doesn't ask for a shipping address when an order has only those.
How shipping works at checkout#
- Your backend sends 1. create a shipping method (once) to Flint
- Your backend sends 2. make it the checkout default (once) to Flint
- Your backend sends 3. create a checkout session for an order to Flint
- Flint returns checkout_session.url to Your backend
- Buyer sends enter a shipping address to Flint
- Flint returns shipping price, tax, and new total to Buyer
- Buyer sends pay to Flint
- Flint sends 4. order.paid webhook, then read the order to Your backend
Steps 1 and 2 are setup. Step 3 is the checkout you already create for each order, unchanged. Step 4 is where your backend gets the shipping address.
1. Create the shipping method#
A delivery method is one choice a buyer can make at checkout. This one ships from your warehouse for $9.00 to any US address:
curl -X POST https://api.withflintpay.com/v1/delivery-methods \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: method-standard-shipping-001" \
-d '{
"name": "Standard shipping",
"type": "shipment",
"status": "active",
"configuration": {
"origin": {
"type": "fixed_location",
"location_id": "loc_1kmn0aExample"
},
"eligibility": {
"country": {"values": ["US"]}
},
"pricing": {
"type": "fixed",
"fixed": {
"currency_options": {
"USD": {"amount": 900, "currency": "USD"}
}
}
},
"estimate": {
"type": "transit_time",
"transit_time": {
"handling_days": {"minimum": 1, "maximum": 1},
"transit_days": {"minimum": 2, "maximum": 5}
}
}
}
}'
nameis the label the buyer sees next to the price.originis the Location the order ships from.eligibilitylimits the method to US addresses. Without it, the method accepts an address in any country.pricingis the rate. Amounts are integers in the currency's minor unit, so900is $9.00. The key incurrency_optionsmust match thecurrencyinside it. A rate of0shows as "Free".estimategives the buyer an arrival date. Flint countshandling_daysand thentransit_daysas business days in the origin Location's time zone, skipping weekends but not holidays, and checkout shows the result, for example "Arrives Oct 6-9". Leaveestimateout to show no date."status": "active"makes the method usable right away. Activation fails if the origin Location isn't active. Withoutstatus, the method is createdinactive, and you activate it later with aPATCHwhose body is only{"status": "active"}.
Flint responds with 201 Created:
{
"data": {
"delivery_method_id": "dmet_1kmn0aExample",
"current_delivery_method_revision_id": "dmetr_1kmn0aExample",
"type": "shipment",
"name": "Standard shipping",
"status": "active",
"version": 1,
"configuration": {
"origin": {"type": "fixed_location", "location_id": "loc_1kmn0aExample"},
"eligibility": {"country": {"values": ["US"]}},
"pricing": {"type": "fixed", "fixed": {"currency_options": {"USD": {"amount": 900, "currency": "USD"}}}},
"estimate": {
"type": "transit_time",
"transit_time": {
"handling_days": {"minimum": 1, "maximum": 1},
"transit_days": {"minimum": 2, "maximum": 5}
}
}
},
"created_at": "2026-10-05T17:04:05Z",
"updated_at": "2026-10-05T17:04:05Z"
},
"request_id": "req_1kmn0aExample"
}
Save delivery_method_id. You need version only when you change the method later.
Tax on the shipping charge#
The shipping charge is taxable by default, and automatic tax charges it at the generally taxable rate where the order ships. To change that for every delivery method, set tax.default_delivery_taxable in your settings. To change it for one method, set configuration.taxable on the method. Sales tax covers how the shipping address becomes the order's tax location.
2. Offer the method on every checkout#
Save the method in your checkout settings, so every checkout offers it without naming it:
curl -X PATCH https://api.withflintpay.com/v1/settings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"checkout": {
"default_delivery_method_ids": ["dmet_1kmn0aExample"]
}
}'
default_delivery_method_ids holds up to 25 methods, and each PATCH replaces the whole list. To add a second method later, send both IDs. Send [] to remove the default. Every method in the list, and every zone, location set, or rate callback it uses, must exist and be active. Otherwise saving returns 400 FULFILLMENT_METHOD_UNAVAILABLE.
Each kind of checkout picks its methods this way:
| Checkout | Methods it offers |
|---|---|
Checkout session created without delivery_method_ids | The default, when the order has items to ship. |
Checkout session created with delivery_method_ids | Only the methods listed. [] offers none. |
Payment link without its own delivery_method_ids | The default, read each time a buyer opens the link, when the order has items to ship. |
Payment link with its own delivery_method_ids | Only the link's methods. |
| Invoice checkout | The default, when the invoiced order has items to ship. |
3. Create the checkout#
Create the checkout session for the order the way you already do. Leave out delivery_method_ids and the session uses the default:
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: checkout-order-1042" \
-d '{
"order_id": "ord_1kmn0aExample",
"redirects": {
"success_redirect_url": "https://example.com/thanks"
}
}'
The session lists the methods it offers in delivery_method_ids:
{
"data": {
"checkout_session": {
"checkout_session_id": "cs_1kmn0aExample",
"status": "open",
"surface": "hosted",
"order_id": "ord_1kmn0aExample",
"delivery_method_ids": ["dmet_1kmn0aExample"],
"delivery_selection_required": true,
"url": "https://checkout.withflintpay.com/checkout/cs_1kmn0aExample#checkout_token=cklt_v1..."
},
"checkout_access": {
"checkout_auth_token": "ckat_v1..."
}
},
"request_id": "req_1kmn0aExample"
}
delivery_selection_required is true because the order has items to ship and the buyer hasn't chosen how yet. Send the buyer to checkout_session.url exactly as returned, as Checkout sessions describes.
The session pins the method as it is when the session is created. If you change the rate afterward, open sessions keep the old one and new sessions get the new one.
If the session's methods can't ship one of the order's items, Flint doesn't create the session and returns 400 FULFILLMENT_METHOD_ASSIGNMENT_UNSATISFIABLE. That happens when the default is empty, or when an item's delivery profile doesn't allow shipment.
What the buyer sees#
The checkout page has a Shipping section with an address form: full name, street address, an optional apartment or unit, city, state, and ZIP code, plus a country list when the method ships to more than one country. Address suggestions appear as the buyer types unless you set customer_collection.enable_address_autocomplete to false.
With one method, checkout chooses it for the buyer. It shows the name, the arrival estimate, and the price, then adds a Shipping line to the order summary and updates the tax and total once the address is in. The buyer can't pay until the address is complete. If the address is outside the method's eligibility, checkout says it doesn't ship there and asks for a different address.
To save the buyer some typing, send an address in customer_collection.prefilled_customer_info.shipping_address, or create the checkout for a customer with a saved shipping address. Checkout fills in the form and prices that address as soon as the buyer opens it.
Apple Pay and Google Pay collect the shipping address and show the shipping price in the wallet's payment sheet.
4. Ship the order#
When the buyer pays, Flint sends order.paid. The webhook doesn't include the address, so read the order:
curl https://api.withflintpay.com/v1/orders/ord_1kmn0aExample \
-H "Authorization: Bearer YOUR_API_KEY"
The shipping details are in delivery_destination and charges:
{
"data": {
"order_id": "ord_1kmn0aExample",
"payment_status": "paid",
"delivery_destination": {
"recipient": {"name": "Ada Lovelace"},
"address": {
"line1": "120 Kent Avenue",
"city": "Brooklyn",
"state": "NY",
"postal_code": "11249",
"country": "US"
},
"source": "delivery_selection",
"delivery_selection_id": "dsel_1kmn0aExample",
"frozen_at": "2026-10-05T17:09:12Z"
},
"charges": [
{
"order_charge_id": "och_1kmn0aExample",
"name": "Standard shipping",
"type": "shipping_fee",
"applied_money": {"amount": 900, "currency": "USD"},
"tax_money": {"amount": 80, "currency": "USD"},
"total_money": {"amount": 980, "currency": "USD"}
}
]
}
}
delivery_destinationis where to ship: the recipient's name, and their phone when checkout asked for one, plus the address.frozen_atis set when the payment succeeds, and the destination can't change after that.- The
shipping_feecharge is what the buyer paid for shipping, with its tax.
Flint also creates a fulfillment for the shipment, in pending, shortly after the order is paid:
Fulfillment and shipping covers packing, adding tracking, and the shipping emails the buyer receives.
Change the rate later#
Send only the configuration keys you're changing, with the method's current version as expected_version. This raises the rate to $10.00 and keeps the origin, the US-only rule, and the arrival estimate:
curl -X PATCH https://api.withflintpay.com/v1/delivery-methods/dmet_1kmn0aExample \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"expected_version": 1,
"configuration": {
"pricing": {
"type": "fixed",
"fixed": {
"currency_options": {
"USD": {"amount": 1000, "currency": "USD"}
}
}
}
}
}'
Each key you send replaces that key's whole value, and keys you leave out keep their values. Send null to clear an optional key: "eligibility": null removes the US-only rule, so the method ships to every country. If someone changed the method after you read it, Flint returns 409 DELIVERY_RESOURCE_VERSION_CONFLICT with the current_version. Read the method again and retry. The new rate applies to checkouts created after the change.
To stop offering the method, remove it from default_delivery_method_ids and from any payment link that lists it, including inactive links. Then deactivate it with a PATCH whose body is only {"status": "inactive"}. Until then, deactivating or archiving it returns 409 DELIVERY_RESOURCE_HAS_DEPENDENCIES.
Errors#
Errors lists every code with its remediation.
Next steps#
- Delivery options: rates by region, free shipping over a threshold, pickup, and local delivery.
- Fulfillment and shipping: ship the order, add tracking, and notify the buyer.
- Build your own checkout: quote and select shipping in your own storefront.
- Delivery configuration API reference: every field on delivery methods.
