Skip to main content

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 own app_ 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 a POST with Content-Type: application/json and the topic discount/request:
  • id, topic, shopId and sentAt are added by the platform. They are inside the signed body, so reject a replay by id or a stale request by sentAt.
  • lineItemId is the stable handle to echo in a line level discount.
  • productId is the catalog id of the line: a product, food, service or digital product. variant is the variant id, or null.
  • unitPrice includes addons and is what the buyer pays per unit.
  • customer is null when nobody is signed in. A guest account carries guest: 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 a discounts 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

  1. 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 amount and that capacity, so a line never goes negative and a second app cannot discount money the first already removed.
  2. Order level next. These are applied in the same order, each capped at what is left of the cart subtotal.
  3. The total is capped at the subtotal. A discount can never take the order total below zero.
  4. A grant reduced to zero is not recorded.
The order stores the total in 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_MS overrides 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.
The resolved result is cached per cart hash for 30 seconds. The hash covers the shop, cart id, currency, subtotal, customer id and guest flag, and every line’s id, product id, variant, quantity, unit price and subtotal. A cart edit is a new key, and a retry of the same cart within the window does not call the apps again. A failed app is cached as no discount for that window.

Signing

Discount requests use the same construction as webhook signature v2 and the app proxy:
The key is the app’s webhook signing secret for the installation’s environment: the development secret for a development install when the app has one, otherwise the production secret. The older 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 under checkoutExtensions 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.
Both would use the same envelope and signing as a discount. The store’s own methods and options would stay unless the merchant enables the app, and the platform would refuse to hide the last available one. Both need a merchant switch and a product decision before a build.

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.