Overview
A checkout function is work your app does during checkout, after the cart has been priced and before payment. Salesive calls your HTTPS endpoint while the buyer waits, validates the answer, and applies what it can. The discount kind is built. It returns money off individual lines or off the order total, with no code from the buyer.
The transport is the same pattern as shipping rates and cart
validation: a signed JSON request, a hard
deadline, an SSRF guard and strict response validation. A checkout never breaks
because an app is down.
Register the discount callback
An installed app registers one callback per installation with its ownapp_
token:
The registration is bound to the installation behind the token, never to
anything in the request. There is one row per installation, so a second
PUT
replaces the first. GET returns 404 when nothing is registered. Uninstalling
the app switches the callback off.
The URL is checked with the webhook URL guard and then with the callback guard
Salesive applies before every call. Both are static plus DNS checks and neither
makes a request. A URL that registers is therefore one that can actually be
called: public, HTTPS, port 443, no credentials and no private or loopback
address.
The request
Salesive sends aPOST with Content-Type: application/json and the topic
discount/request:
id,topic,shopIdandsentAtare added by the platform. They are inside the signed body, so reject a replay byidor a stale request bysentAt.lineItemIdis the stable handle to echo in a line level discount.productIdis the catalog id of the line: a product, food, service or digital product.variantis the variant id, ornull.unitPriceincludes addons and is what the buyer pays per unit.customerisnullwhen nobody is signed in. A guest account carriesguest: true. No name, email, phone or address is sent.- Amounts are JSON numbers in the shop currency, rounded to two decimals.
The response
Answer with adiscounts array:
At most 20 discounts are read from one app. Validation is entry by entry: a
malformed entry is dropped without dropping the rest. A
lineItemId that is not
one of the request’s lines is dropped and is never widened to an order level
discount, so a typo cannot turn a line discount into money off the whole order.
How Salesive applies the discounts
- Line level first. Discounts are applied in the order the apps were loaded
(oldest registration first), then in the order they appear in the response.
Each line has a remaining capacity equal to its subtotal. A discount takes the
smaller of its
amountand that capacity, so a line never goes negative and a second app cannot discount money the first already removed. - Order level next. These are applied in the same order, each capped at what is left of the cart subtotal.
- The total is capped at the subtotal. A discount can never take the order total below zero.
- A grant reduced to zero is not recorded.
order.discount, so the order total, the platform
fee rule and every existing reader keep working. Each applied grant is also
stored in order.discountApplications:
discountApplications is absent on an order from a store with no discount
functions. The platform fee is computed on the discounted subtotal, exactly as
it is when a coupon is applied. A coupon applied after the order is created
stacks on what is left after the app discounts and is capped at the subtotal.
Timeouts and failures
- Each app has 2 seconds, and all apps are called in parallel.
APP_DISCOUNT_TIMEOUT_MSoverrides the deadline, and the client clamps it to 5 seconds. The deadline covers DNS, connect, headers and body. - A timeout, a non 2xx answer, a redirect, a response over 64 KB, a body that is not a JSON object, a blocked URL, a missing signing secret or an internal error contributes no discount.
- The checkout never breaks. On any failure the resolved result is no discount, and order creation carries on. A store whose discount app is down still sells at the normal price.
- Failures are not shown to the buyer. They are logged with the app, installation, reason and duration.
Signing
Discount requests use the same construction as webhook signature v2 and the app proxy:X-Salesive-Hmac-SHA256 header, a base64 HMAC-SHA256 of the raw body,
is sent too, so an app that already verifies carrier rate and validation calls can
reuse that code. Both headers sign the same request body bytes.
Verify the raw bytes before parsing, and reject a timestamp more than 300
seconds from your clock, in either direction. An app with no signing secret is
never called.
Releases
When you create a release draft, Salesive records the checkout callbacks the app had registered undercheckoutExtensions in the release snapshot, one row per
installation and kind. Publishing, a staged rollout and rollback carry that
record with the version.
The snapshot is an audit and rollback record. Publishing does not rewrite the
live registrations: the checkout path still reads the installation’s
AppCheckoutExtension row, so publishing a release can never move a store’s
checkout to a URL the merchant did not register.
The other kinds
Payment customization and delivery customization are designed and not live.- Payment customization would run when the payment step is built, after the order exists. It would hide, rename or reorder payment methods, and add a short message next to one. It would never create a payment session or mark an order paid.
- Delivery customization would run at the shipping step. It would rename, reorder or hide the options the store already has, and add a short message. It would not change a price; a new price belongs to the shipping rates kind.
Next steps
Checkout extensions
Shipping rates and cart validation, the other synchronous checkout calls.
Webhooks
Signature v2, retries and the full topic list.
Releases
How a release snapshot freezes delivery config.
Scopes & permissions
What
WRITE_DISCOUNT_FUNCTIONS grants and how the merchant sees it.
