Skip to content
On this page
How-to Checkout rules 5 min read Updated Sep 28, 2026

Validate checkout with your own API

In short

Add a Validate Via API action to a checkout rule and enter your API URL. While the rule matches, the Rules Executor POSTs checkout data as JSON to that URL. Your endpoint replies with an actions array and an optional errors array; each error blocks checkout and shows its message to the buyer.

✓ Applies to

  • Deluxe plan only (checkout rules aren't included in Advanced)
  • Shopify Plus checkout, with the Rules Executor block added
  • Developers who run an HTTPS endpoint that accepts cross-origin POST requests

✕ Doesn't apply to

  • Server-side order validation after payment. The call happens in the buyer's browser during checkout
  • Custom request headers. The API Headers field isn't sent with the request

Before you start

  • Add the Rules Executor app block to your checkout and allow it to block checkout progress. How to add it.
  • An HTTPS endpoint that accepts POST with a JSON body and answers with JSON.
  • CORS enabled on that endpoint. Shopify runs checkout extensions in a sandbox in the buyer’s browser, so your server must answer with Access-Control-Allow-Origin: * and handle the preflight OPTIONS request for the Content-Type and X-checkout-session-token headers.

Steps

  1. In Checkout Ninja, open Rules and click New checkout rule.
  2. Enter a Title, up to 30 characters.
  3. (Optional) Under Condition, add conditions to limit when the API is called. With no conditions, it’s called on every checkout.
  4. Under Action, click Add Action and choose Validate Via API.
  5. Enter your endpoint in API URL.
  6. (Optional) In Input Query, list the fields to send. Leave it empty to send everything. See Input query.
  7. Set Status to Active and click Save.

The request

  • Method: POST
  • Headers:
    • Content-Type: application/json
    • X-checkout-session-token: a Shopify session token for the checkout, when Shopify provides one
  • Body: the checkout data as JSON, filtered by Input Query if you set one.

The full body has these top-level fields:

FieldWhat it holds
deliveryAddressShipping address: address1, address2, city, company, countryCode, firstName, lastName, phone, provinceCode, zip
appliedGiftCardsGift cards on the checkout, with amountUsed, balance and lastCharacters
customerThe logged-in customer, if any
emailBuyer’s email
cart.cost.totalAmountCart subtotal, as amount and currencyCode
cart.linesCart lines with id, quantity, merchandise and cost
cart.deliveryGroupsDelivery groups and selected delivery options
cart.attributesCart attributes as key / value pairs
cart.metafieldsCart metafields used by your rule conditions
selectedPaymentOptionsThe payment options the buyer has selected
discountsAllocationsOrder and shipping discounts
productDiscountsProduct discounts from cart lines
i

Treat the session token as a signal, not proof. It’s issued to Checkout Ninja’s app, so you can’t verify its signature with your own credentials. Don’t return anything sensitive, and validate every field you receive.

Input query

Input Query takes a GraphQL-style selection of the fields above. Only those fields are sent.

{ deliveryAddress { zip countryCode } email }
{
  cart {
    cost { totalAmount }
    lines { id quantity merchandise { id title } }
  }
}

A plain comma-separated list also works: email, customer. A field with no sub-selection is sent whole, and unknown fields are skipped. If the query can’t be parsed, the full body is sent.

The response

Reply with JSON in this shape:

{
  "actions": [],
  "errors": [
    {
      "message": "We can't deliver to this postcode.",
      "target": "$.cart.deliveryGroups[0].deliveryAddress.zip"
    }
  ]
}
  • actions (required). An array, even if empty. If it’s missing, the rules run fails and nothing blocks the buyer.
  • errors (optional). Each entry blocks checkout and shows message. target places it next to a checkout field.

Targets you can use in errors:

  • $.cart.buyerIdentity.email
  • $.cart.buyerIdentity.phone
  • $.cart.deliveryGroups[0].deliveryAddress. followed by address1, address2, city, company, countryCode, firstName, lastName, phone, provinceCode or zip

Actions your API can return

Each entry in actions is { "action": "<name>", "value": { … } }. Apart from set_message, actions are passed to Checkout Ninja’s payment customization, merged with any Hide payment method rule actions.

actionvalueEffect
hide (or hide_payment, hide_payment_method){ "name": "Cash on Delivery" }Hides payment methods whose name contains name, ignoring case. name can also be an array.
hide_all_payment_except{ "name": "Card" }Hides every payment method whose name doesn’t contain name.
hide_all_payment_methods{}Hides every payment method.
rename (or rename_payment){ "currentName": "COD", "newName": "Pay on delivery" }Renames matching payment methods.
move_to_position (or move_payment_to_position){ "name": "COD", "position": "1" }Moves matching payment methods to a position, starting at 1.
set_messageanyStored in the cart attribute _checkout_ninja_message. Nothing is shown to the buyer.
!

Only return action names from this table. An unknown name, such as hide_cod_payment, makes the payment customization fail, and then no payment method is hidden, including by your Hide payment method rules.

Caching

Each response, errors included, is kept in memory in the buyer’s checkout for 5 minutes, keyed by the API URL and the exact data sent. The same data within 5 minutes reuses the cached response without calling your API. Use Input Query to send only the fields you validate, so unrelated checkout changes don’t cause new calls.

Verify it worked

  1. Point API URL at your endpoint and return a fixed error for a test value, such as one postcode.
  2. Open your checkout and enter that value. Try to continue. Your message appears and checkout doesn’t move forward.
  3. Enter a different value. You can continue.
  4. Check your server logs. Requests arrive as POST with only the fields from your Input Query.

Limitations

  • Shopify Plus only, because the rule runs in the Rules Executor checkout block.
  • The call is made from the buyer’s browser, so the request is visible to them and your endpoint must be public.
  • There’s no timeout. While the call is in flight, the executor holds checkout progress, if you’ve allowed it to block progress. Keep your endpoint fast.
  • API Headers is shown in the editor but not sent.
  • If your endpoint fails or returns invalid JSON, checkout isn’t blocked, and other rule actions in that same run are skipped.

Troubleshooting

The API is never called. Check the browser’s network tab at checkout. If there’s a failed OPTIONS request, your CORS headers are missing. Otherwise check the rule is Active, its conditions match, and the Rules Executor is in your checkout.

My errors don’t block checkout. Check the response has an actions array and an errors array of objects with message. Then check the Rules Executor is allowed to block checkout progress. Step-by-step fix.

My API sees stale data. The response is cached for 5 minutes for identical data. Change the test data, or open a new checkout, to force a new call.

Frequently asked

When is my API called? +

Whenever the rule's conditions match and the checkout data you send has changed. The Rules Executor re-runs rules each time the checkout changes, and caches each response for 5 minutes.

What happens if my API is down? +

The whole rules run fails for that update, so no rule errors block the buyer and checkout can continue. Other rule actions from that same run are skipped too.

Can I send an API key in a header? +

Not at the moment. The API Headers field is saved but not sent. The request carries only Content-Type and X-checkout-session-token.

Does the HTTP status code matter? +

No. The executor reads the JSON body whatever the status code, so a 500 response with an errors array still blocks checkout.

Related guides

Still stuck? Open the app and use Support to chat with the team, or contact us. Don't have the app yet? Install Checkout Ninja.