DevelopersCustom API

Build with the noli loyalty API

Awarding points, redeeming rewards, and minting coupons — all over plain JSON. No SDK. Any backend.

Overview

Use the Custom API integration when your store runs on a platform we don't natively support yet (headless commerce, custom backends, ERP systems, etc.). You'll do two things:

  1. Send orders to Noli from your backend so we can award points to the matching customer.
  2. (Optional) Expose a promo-code endpoint we can call when a customer redeems a reward — so the code already exists in your e-commerce platform.

Both flows are JSON over HTTPS. There's no SDK — any language with an HTTP client works.

Authentication

Every request to the noli API is identified by your public API key, sent as the x-api-key header.

You can find your key in Settings → Integrations → Custom API on the admin panel. Treat it like a password — don't commit it to git, don't ship it to browsers, don't paste it into chat. If it leaks, regenerate from the same screen.

x-api-key: pk_live_••••••••••••••••••••••••••••••

Note: requests are also identified by business_id in the body — that's the canonical tenant identifier. The API key prevents requests from other merchants reaching your business by accident.

Earn points

Your backend → Noli

POSThttps://qremotwvbhxqkzmxrqzs.supabase.co/functions/v1/public-add-points

Call this once per completed order. Noli looks up the customer (by phone or email), applies your earn rules, and awards points. If the customer isn't registered yet, the order is held as pending and points are awarded automatically when they sign up.

Requests are idempotent on order_id — sending the sameorder_id twice is safe and returns the original result.

Request body

FieldTypeRequiredDescription
business_idstring (uuid)requiredYour business ID. Visible in the admin panel URL.
order_idstringrequiredYour internal order identifier. Used for idempotency.
amountnumberrequiredOrder subtotal in your store currency.
customer_emailstringoptionalCustomer email. Provide either email or phone (or both).
customer_phonestringoptionalCustomer phone in E.164 format.
order_itemsarrayoptionalOptional line items — used for catalog analytics.

Order item fields

Optional, but recommended. Each entry in order_items takes the shape:

FieldTypeRequiredDescription
product_skustringoptionalStock keeping unit. Helps catalog matching.
product_idstringoptionalYour internal product ID.
product_namestringrequiredDisplay name of the product.
quantitynumberrequiredUnits purchased.
unit_pricenumberrequiredPer-unit price in your store currency.

Response shapes

Four shapes you'll see — all return HTTP 200 except validation errors (4xx / 5xx).

  • Awarded — points went on the customer's balance.
  • Pending — customer not registered yet, order parked.
  • Duplicate — same order_id already processed (idempotency).
  • Error — validation or server failure.

Code examples

Drop-in clients for the most common backends.

Thank-you widget

Drop-in client snippet for the order-confirmation page

The widget is a small JS embed you place on your order-confirmation / thank-you page. It posts the order to noli and renders a "Claim your points" CTA (with QR code for mobile handoff) that takes the customer to your customer portal. The portal handles registration with Viber, email, or phone as options, and credits the points on success. Same idempotency guarantee as the server-to-server endpoint — duplicate order_id is safe.

Use the widget when:

  • You want a one-tap claim CTA without writing the UI yourself.
  • Your platform doesn't expose a server-side webhook for order completion.
  • You want the merchant's brand colors applied automatically.

Prefer the server-to-server public-add-points endpoint if you don't need a customer-facing CTA or need to award points outside the customer's session (e.g. async fulfilment).

Endpoint

POSThttps://qremotwvbhxqkzmxrqzs.supabase.co/functions/v1/create-pending-transaction-and-redirect

The widget posts here under the hood. The primary CTA target is portal_redirect_url — the merchant's loyalty portal with the order encoded as a query param. The portal then handles the claim (already-awarded vs needs-registration vs duplicate) and offers Viber, email, or phone as registration options when needed.

Three states are possible:

  • Awarded — points already credited (Viber subscriber). Response includes points_added and new_balance.
  • Pending — order parked. registration_required: true; the portal will award points after the customer registers.
  • Duplicate — same order_id already processed; already_processed: true.

Note: viber_redirect_url is also returned but is deprecated — it points to a raw viber:// deep link, which is what widget v1 used. New integrations should use portal_redirect_url so the customer can pick a registration channel instead of being forced into Viber.

Embed snippet

Drop this into your thank-you page template. The script readswindow.NoliLoyalty for configuration, so you can inject merge tags from your platform (Liquid, Twig, ERB, etc.).

FieldTypeRequiredDescription
business_idstring (uuid)requiredYour business ID — visible in the admin panel URL.
order_idstringrequiredYour internal order id. Used for idempotency.
amountnumberrequiredOrder subtotal in your store currency.
customer_emailstringoptionalCustomer email. Pass either email or phone (or both).
customer_phonestringoptionalCustomer phone in E.164 format.
brand_primarystring (hex)optionalPrimary button color. Defaults to your business setting.
brand_secondarystring (hex)optionalHover / gradient end color. Defaults to your business setting.

Brand colors

The widget uses the same Primary + Secondary colors you picked in Branding step during onboarding (or set later under Settings → Branding). When you don't pass brand_primary orbrand_secondary in the snippet, the script fetches them from your business profile so the button stays in sync if you rebrand.

If you do want to override (e.g. seasonal campaign), pass hex values in window.NoliLoyalty:

window.NoliLoyalty = {
  // …
  brand_primary:   '#FF7A6B',  // overrides for this page only
  brand_secondary: '#C866B8',
};

The script renders a 135° linear gradient from primary to secondary, matching the loyalty card on the customer portal.

Per-platform tips

  • Shopify — paste the snippet into Settings → Checkout → Order status page → Additional scripts. Liquid merge tags ({{ order.id }}, {{ customer.email }}) are evaluated server-side.
  • WooCommerce — hook into woocommerce_thankyou in your theme's functions.php (or via a snippets plugin) and inject the script tag with PHP-rendered values.
  • BigCommerce / OpenCart / custom — render the snippet into the order success template and substitute the order id, total and customer fields with the platform's variables.
  • SPA / headless — call the endpoint manually (see "Manual fetch" tab) and render your own button using the response.

Promo code generator endpoint

Noli → your backend

When a customer redeems a reward, Noli normally generates a code internally and shows it to the customer. If you'd rather mint the code in your own system (so the customer gets a code that already exists in your e-commerce platform, ERP, or POS), expose an endpoint and we'll call it on each redemption.

Configure the endpoint URL, HTTP method, and parameter mapping under Settings → Rewards → Promo code method → Custom API on each reward.

What Noli sends

HTTP method: GET or POST (you choose). Headers include Content-Type: application/json on POST.

Static params: any key/value pairs you configured — useful for an authentication token your endpoint requires.

Dynamic params: resolved from the redemption context. Available paths:

FieldTypeRequiredDescription
user.idstringoptionalLoyalty member id.
user.namestringoptionalMember name (if known).
user.emailstringoptionalMember email (if known).
user.phonestringoptionalMember phone E.164 (if known).
user.points_balancenumberoptionalMember balance after redemption.
reward.idstringoptionalReward id being redeemed.
reward.namestringoptionalReward name.
reward.cost_pointsnumberoptionalHow many points the customer spent.
reward.discount_valuenumberoptionalPercent or fixed amount (ignored for free_shipping / free_item).
reward.reward_typestringoptional"discount_percent", "discount_fixed", "free_shipping", "free_item" or "tier_unlock".
reward.discount_typestringoptionalNormalised flag: "percent" | "fixed_eur" | "free_shipping" | null.
reward.is_percentstringoptional"1" when percent discount, else "0".
reward.is_fixedstringoptional"1" when fixed-amount discount, else "0".
reward.is_free_shippingstringoptional"1" for a free-shipping reward (map to a free-shipping coupon; amount is 0/irrelevant), else "0".

What you return

Respond with JSON. Noli reads two paths:

  • response_code_path — the path to the generated code (default code).
  • response_success_path — optional. If set, must resolve to a truthy value for the redemption to succeed.

If your endpoint returns a non-2xx status or the response doesn't contain a code at the expected path, redemption fails and the customer's points are not spent — so transient errors are safe.

Example endpoint

A minimal Express handler that mints a coupon in your e-commerce platform.

Need help?

Reach out at support@heynoli.ai with your business_id and a sample request — we'll get back the same day.

Also see: How loyalty rules work