Checkouts
A checkout turns an offer into an order. Harbor builds the cart at the store, checks the user's rules against the real total, and tells you exactly what has to happen next.
Create a checkout
Create it from an offer, or straight from a product link. Both need the user's token.
curl https://www.useharbor.io/v1/checkouts \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "Harbor-User-Token: $USER_TOKEN" \
-H "content-type: application/json" \
-d '{
"offer_id": "ofr_071f4m634y1x453v055k",
"quantity": 1,
"buyer": { "email": "sam@example.com" },
"shipping_address": {
"name": "Sam Shopper", "line1": "1 Test St", "city": "Chicago",
"region": "IL", "postal_code": "60601", "country": "US"
}
}'| Field | What it does |
|---|---|
offer_id or product_url |
Exactly one. Use product_url for major retailers, whose prices are read in the user's browser. |
item_ref |
A specific variant from the offer's variants. Defaults to the offer's own item_ref. |
quantity |
1 to 20. |
buyer |
email and phone. Platform stores usually need an email. |
shipping_address |
Two-letter country. Major retailers use the address saved in the user's account instead. |
Statuses
| Status | Meaning | What you do |
|---|---|---|
building |
Harbor is creating the cart at the store. | Wait. |
requires_user_action |
Something has to happen before the order can be placed. | Read next_action. |
ready_to_confirm |
The total is known and within the user's rules. | Call confirm. |
confirming |
Harbor is placing the order. | Wait. |
completed |
The order was placed. order_id is set. |
Follow the order. |
failed |
It can't go through. error says why. |
Show error.display_message to the user if present. |
canceled |
You canceled it. | Nothing. |
Every change sends a checkout.updated webhook, plus a specific event for requires_user_action, completed and failed.
Next actions
When the status is requires_user_action, next_action.type tells you who does what.
| Type | Who | What to do |
|---|---|---|
confirm |
The user | Open next_action.url. The user reviews the total and approves. The checkout then returns to ready_to_confirm and you call confirm. |
user_input |
You or the user | Provide next_action.fields. Address or email: call update. A code the store sent the user: call user input. |
redirect_to_merchant |
The user | Open next_action.url, the store's own checkout page. The user finishes there with one tap. |
on_device |
The user's browser | Major retailers. The Harbor extension is preparing the order; the user taps Place order in their browser. Wait for the webhook. |
reconnect |
The user | Their store connection needs attention. Send them through Approve again. |
Each next_action has a plain-language description you can show as-is.
Confirm
When the total is at or above the user's confirm_above, or the price changed since the offer, requires_user_confirmation is true and the next action is confirm:
{
"status": "requires_user_action",
"requires_user_confirmation": true,
"next_action": {
"type": "confirm",
"url": "https://www.useharbor.io/confirm/chk_2w3i5a4h…?t=…",
"description": "This order is above the user's confirmation threshold."
}
}Open the URL for the user. If you opened it with window.open, Harbor posts { type: "harbor:confirmed", checkout_id, decision } back to your page when they answer. You'll also get a checkout.updated webhook with ready_to_confirm.
Update buyer details
If the store asks for an address or email (user_input with fields like shipping_address or buyer.email), send them and Harbor re-prices the cart:
curl https://www.useharbor.io/v1/checkouts/chk_…/update \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "Harbor-User-Token: $USER_TOKEN" \
-H "content-type: application/json" \
-d '{ "buyer": { "email": "sam@example.com" }, "shipping_address": { … } }'A new total needs fresh confirmation if the user's rules call for it.
User input
Some stores send the user a verification code while placing the order. Ask the user for it and pass it on:
curl https://www.useharbor.io/v1/checkouts/chk_…/user_input \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "Harbor-User-Token: $USER_TOKEN" \
-H "content-type: application/json" \
-d '{ "fields": { "verification_code": "123456" } }'Place the order
curl -X POST https://www.useharbor.io/v1/checkouts/chk_…/confirm \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "Harbor-User-Token: $USER_TOKEN" \
-H "Idempotency-Key: confirm-chk_…"With an Idempotency-Key, retrying after a timeout returns the first result instead of placing a second order. Confirm only works in ready_to_confirm; otherwise it returns USER_ACTION_REQUIRED. Harbor checks max_per_day again at this point. The response is the checkout, usually completed with an order_id. It can also come back requires_user_action (for example, a code from the store) or failed (for example, PAYMENT_DECLINED).
Cancel
curl -X POST https://www.useharbor.io/v1/checkouts/chk_…/cancel \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "Harbor-User-Token: $USER_TOKEN"Any open checkout can be canceled. For major retailers this also stops the work in the user's browser.
What Harbor guarantees
- An order is only placed when the user's rules allow it, or the user confirmed it themselves.
- Rules are checked against the store's real total, including shipping and tax.
- A price change since the offer always comes back to the user.
- At major retailers, the final tap is always the user's.