LESS INTEGRATION. MORE POSSIBILITY.

Your POS doesn't need
to understand loyalty.

Just send us your products and orders.
Letstamp handles the rest.

Campaigns, reward eligibility, customer interactions, reward selection, discount calculations, and loyalty transactions — all handled by Letstamp.

ONCE

Connect your catalog.

Prepare menu JSON → configure your branch.

EVERY SALE

Send. Follow. Apply.

Send an order → follow the response → apply the discount → confirm checkout when required.

Your POS owns the sale. Letstamp handles loyalty; it does not take payment or close your POS check.

Prepare your menu JSON

Host your product catalog at a URL that Letstamp can read. Set it up once, then keep the catalog updated as products change.

Complete menu example · JSON
{
  "menu_items": [
    {
      "id": "B01",
      "product_name": "Coffee"
    },
    {
      "id": "I03",
      "product_name": "Sandwich"
    }
  ]
}
Required field What to provide
menu_items An array containing the complete catalog. No pagination is implemented.
menu_items[].id A stable, non-empty product ID. Use the same value as items[].product_id in orders. Entries without an ID are skipped.
menu_items[].product_name The product name. In an order, this field is called name.

View the official sample JSON ↗

Sample URL
https://default.hooks.api.letstamp.com/v100/products_sample.json
Import, access, and product mapping

Letstamp makes a GET request to your configured Products API URL when import is triggered. This is your POS-hosted URL; there is no fixed Letstamp product-upload route. Businesses map imported products to earning triggers, reward products, and required products. Reward mappings can vary by customer class.

The importer does not send the integration accessToken header or define custom authentication headers. Confirm product-endpoint access requirements and the refresh schedule during onboarding.

Configure your branch in Letstamp

In Letstamp Partners, open the branch integration settings and connect your POS.

Branch integration Letstamp Partners / Configuration guide
SET UP ONCE
Setting Your configuration
Integration Provider Select Letstamp API.
Branch ID Required and unique. Generate an ID or define your own, such as IST-01. Use it as branch_id.
API Key Automatically generated by Letstamp. Send it in the accessToken request header.
Products API URL Required. Your hosted URL returning the menu JSON from Step 1.
Checkout Completion Complete after POS checkout confirmation (Recommended)
or Complete when the order is submitted.
Reward Selection Customer selects from the app, or business selects from the partner panel.
Status Active / Inactive. Activate the integration before sending requests.
Setup is done.Steps 1 and 2 are not repeated for each sale. The integration, business, and branch must be active with a valid membership.
Authentication for every request

Send requests from your POS backend or trusted runtime. Match the API key with the configured branch identifier in the JSON body. The branch identifier is not a customer identifier.

Required headers
accessToken: YOUR_ACCESS_TOKEN
Content-Type: application/json

Examples contain safe placeholders. Browser/CORS support needs confirmation.

Choose your integration mode

The branch setting controls the mode. There is no mode field in an API request.

MODE B

Without checkout confirmation

  1. Send the order POST /v100/
  2. Check check_status
  3. Redemption available Hold checkout → poll status Selection → apply discount once Release checkout
    No redemption Continue normally
  4. Complete the sale in your POS
  5. Done. No checkout request.

Fewer requests. Loyalty completes on selection, so later POS cancellations cannot be synchronized through a documented reversal endpoint.

In both modes, no available redemption completes loyalty on submission. Declining redemption in Letstamp also completes loyalty without a checkout confirmation.

TAILOR THIS GUIDE

Poll for selection, apply the discount, complete the sale, then confirm the selected redemption.

Send order → Follow response → Apply discount → POS checkout → Confirm selection

Send orders to Letstamp

YOU SEND → LETSTAMP RESPONDS → YOUR POS DOES

Capture the customer's current Letstamp short code. Send it with the order and products. Letstamp calculates eligibility and handles customer or cashier selection.

POST https://default.hooks.api.letstamp.com/v100/

Headers: accessToken: YOUR_ACCESS_TOKEN · Content-Type: application/json

You send · order JSON
{
  "order_id": "AD-45821",
  "branch_id": "IST-01",
  "option": "123123",
  "items": [
    {
      "product_id": "B01",
      "name": "Coffee",
      "qty": 2,
      "unit_price": 120
    },
    {
      "product_id": "I03",
      "name": "Sandwich",
      "qty": 1,
      "unit_price": 140
    }
  ]
}
Field Send as Meaning & current handling
order_id String Your order/check identifier. Non-empty; retain the same value for status and checkout. Recommendation: use globally unique IDs across locations. Re-submission is not a safe update or retry mechanism; see the retry notes below.
branch_id String Configured integration branch identifier, validated together with the token.
option String Customer short code. Keep it as a string to preserve any leading zeros. It must be unused, current, and valid for the business's Letstamp app context.
items Array of objects Products on the check. An empty array returns no_item. The body must contain this collection.
items[].product_id String Exact product ID used in the product mapping. Items with an empty product ID are skipped.
items[].name String Product name. The handler reads this field; include it. Whether omission is supported needs confirmation.
items[].qty Number, whole units Quantity. The current handler floors numeric quantities to whole units and processes each unit separately. Recommendation: send positive integers; fractional/weighted sales need confirmation.
items[].unit_price Number Price per unit, not the line total. Decimals are supported. Use an unformatted JSON number, without a currency symbol or thousands separator.

The total is derived from quantities × unit prices (380.00 in this example). No total or currency field is read.

Letstamp responds → your POS acts

No redemption available
Response · JSON
{
  "status": true,
  "check_status": "check_processed",
  "message": "Order processed successfully."
}

Your POS → Continue normal checkout. Loyalty is already complete; do not poll or send checkout confirmation.

Reward or cashback available
Response · JSON
{
  "status": true,
  "check_status": "pending_redemption",
  "message": "Reward Redemption Is Pending."
}

Your POS → Hold the checkout screen and start status polling in Step 4. This response is possible in both modes.

Letstamp response · English examples What your POS should do
{"status":true,"check_status":"already_processed","message":"This order has already been processed."} Do not process loyalty again. Reconcile with the existing POS sale.
{"status":true,"check_status":"no_item","message":"Order contents not found."} Stop this attempt. Fix the item list and non-empty product IDs before a deliberate new submission.
{"status":true,"check_status":"unexpected_error","message":"Unexpected error."} Stop and inspect the order ID and items. Do not assume the write had no effect or retry blindly.
{"status":false,"message":"Unexpected Error"} Missing or empty access token. Check the accessToken header.
{"status":false,"message":"Branch membership is not active"} Check the token/branch pair, integration status, business/branch status, and membership.
{"status":false,"message":"Invalid user code"} Stop this attempt and resolve the invalid or expired code. Submission and checkout also reject a consumed code. Do not replace the code on an in-flight order without reconciliation.
Validation, errors, and safe retries

Inspect both status and check_status: status: true alone is not success. Messages are localized; do not branch on English text. Missing items or an empty order ID can return unexpected_error only if parsing and earlier checks pass. Empty/malformed JSON has no guaranteed JSON error response.

Never resubmit an order to poll or update it: re-submission can replace a pending selection. A consumed code can be rejected before the already-processed check. Use globally unique order IDs across locations and deployments. On network, HTTP, empty-body, or JSON errors, stop and reconcile before another write. No automatic retry contract is defined.

Field lengths, item-count limits, weighted quantities, tax, currency, and rounding policies require onboarding confirmation.

Handle rewards

YOU SEND → LETSTAMP RESPONDS → YOUR POS DOES

Only start polling after status: true and check_status: "pending_redemption" from the order. Keep checkout on hold while the customer or cashier makes a choice through Letstamp.

POST https://default.hooks.api.letstamp.com/v100/?q=status

Send the accessToken header and the same three identifiers. No items, action, or discount are needed.

You send · status JSON
{
  "order_id": "AD-45821",
  "branch_id": "IST-01",
  "option": "123123"
}
Still waiting
Response · JSON
{
  "status": true,
  "check_status": "pending_redemption"
}

Your POS → Keep checkout on hold. Poll again within your bounded waiting policy.

Completed without a selection
Response · JSON
{
  "status": true,
  "check_status": "completed"
}

Your POS → Stop polling. Release checkout without a redemption discount. No checkout confirmation is needed.

Reward discount ready
Response · JSON
{
  "status": true,
  "check_status": "redemption_selected",
  "action": "spendReward",
  "discount_amount": 120
}

Your POS → Stop polling. Apply the monetary discount once, then release checkout.

Cashback discount ready
Response · JSON
{
  "status": true,
  "check_status": "redemption_selected",
  "action": "spendCashback",
  "discount_amount": 5
}

Your POS → Stop polling. Apply the monetary discount once, then release checkout.

With Checkout: after applying the selected discount and successfully completing the POS sale, continue to Step 5.

Letstamp response What your POS should do
true + pending_redemption Continue polling; keep checkout on hold until your local deadline.
true + redemption_selected Stop polling → apply discount_amount exactly once → release checkout. Supported selection actions are spendReward and spendCashback.
true + completed Stop polling → release checkout without a discount or confirmation.
status: false, unexpected state, or transport error Stop polling → release the automated hold into an operator recovery flow. Do not infer a discount or silently finish an uncertain sale.
Letstamp response · English examples What your POS should do
{"status":false,"message":"Order contents not found."} No matching order was found. Stop polling and verify the original identifiers; reconcile instead of re-submitting automatically.
{"status":false,"message":"Unexpected Error"} Missing or empty access token. Check the accessToken header.
{"status":false,"message":"Branch membership is not active"} Check the token/branch pair, integration status, business/branch status, and membership.
{"status":false,"message":"Invalid user code"} Stop this attempt and resolve the invalid or expired code. Submission and checkout also reject a consumed code. Do not replace the code on an in-flight order without reconciliation.
Never wait indefinitely.Polling every 1–3 seconds, with one request in flight, is a client recommendation. Choose a finite local deadline and request timeout. At that limit, stop polling and let the operator resolve the sale. The API does not specify a server polling timeout, automatic cancellation, retry schedule, or reversal operation. A local timeout does not cancel loyalty.
Discounts, short codes, and lifecycle details

discount_amount is a monetary amount, not a percentage. Amounts above are illustrative. No reward ID, product allocation, point-conversion rate, or currency is returned. Apply the order-level amount once using decimal-money handling; do not recompute eligibility. A reward may be a partial discount.

The status lookup supports both modes. It accepts a used code while the code remains within its validity window. Order and checkout requests still require an unused code. The documented acceptance window is 15 minutes from code creation; confirm the configured lifetime during onboarding. Code expiry can interrupt a long checkout.

A saved selection can continue returning redemption_selected even after loyalty completion. Repeated reads do not spend rewards or award earnings. Persist whether you have already applied the discount. The completed status response means completed with no saved selection.

Stop and seek operator recovery for an unknown action or an invalid amount; never apply an unvalidated response.

Confirm checkout Optional mode

YOU SEND → LETSTAMP RESPONDS → YOUR POS DOES

When the POS successfully completes the sale, notify Letstamp. This step applies to a selected redemption in With Checkout mode.

POST https://default.hooks.api.letstamp.com/v100/?q=checkout
Successful POS payment comes first.Apply the selected discount → release checkout → successfully tender/close the sale → send this request. Never confirm on selection, payment initiation, failed payment, or cancellation.

Send the accessToken header and the same identifiers. Letstamp reads the saved selection; do not send the action or discount.

You send · checkout JSON
{
  "order_id": "AD-45821",
  "branch_id": "IST-01",
  "option": "123123"
}
Loyalty completed
Response · JSON
{
  "status": true,
  "check_status": "completed"
}

Your POS → Record the successful confirmation. No further API request is required.

Letstamp response · English examples What your POS should do
{"status":false,"check_status":"pending_redemption"} No saved selection yet. Do not repeat confirmation. Investigate the POS flow and reconcile the already-paid sale.
{"status":false,"message":"Order not found"} Verify the identifiers and configured mode. Checkout looks for an order waiting for checkout confirmation. Reconcile before another write.
{"status":false,"message":"Unexpected error. - Invalid redemption action"} The saved selection action is unsupported. Stop and reconcile with Letstamp.
{"status":false,"message":"Checkout failed"} The checkout transaction could not start. Record the failure and reconcile before retrying.
{"status":false,"message":"Unexpected error."} A transaction or commit error occurred. Treat the result as uncertain and reconcile before retrying.
{"status":false,"message":"Unexpected Error"} Missing or empty access token. Check the accessToken header.
{"status":false,"message":"Branch membership is not active"} Check the token/branch pair, integration status, business/branch status, and membership.
{"status":false,"message":"Invalid user code"} Stop this attempt and resolve the invalid or expired code. Submission and checkout also reject a consumed code. Do not replace the code on an in-flight order without reconciliation.
Confirmation, concurrency, and uncertain outcomes

Do not send parallel checkout confirmations. Repeating a successful request may return an invalid-code error because completion consumes the customer code. If a request times out, check the outcome with Letstamp before retrying.

There is no documented cancellation, refund, or reversal operation. Agree recovery after failed payment or uncertain confirmation with Letstamp. This step is not required in Without Checkout mode, or when loyalty already completed without a selected redemption.

See How Simple It Is

One order. Poll only when needed. Apply the result. Confirm a selected redemption after the sale.

Replace YOUR_ACCESS_TOKEN and the sample order data with your branch credentials and sale details. Each snippet sends one request; run it at the step described. PHP uses cURL. Node.js uses built-in fetch (run as an .mjs file).

1. Send the order

Send the current order and customer code. If the response is pending_redemption, hold checkout and start the status requests below. If it is check_processed, continue normally.

PHP · order request
<?php
$body = <<<'JSON'
{
  "order_id": "AD-45821",
  "branch_id": "IST-01",
  "option": "123123",
  "items": [
    {
      "product_id": "B01",
      "name": "Coffee",
      "qty": 2,
      "unit_price": 120
    },
    {
      "product_id": "I03",
      "name": "Sandwich",
      "qty": 1,
      "unit_price": 140
    }
  ]
}
JSON;
$ch = curl_init('https://default.hooks.api.letstamp.com/v100/');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_TIMEOUT => 10,
  CURLOPT_HTTPHEADER => ['accessToken: YOUR_ACCESS_TOKEN', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => $body
]);
$raw = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw === false || $http < 200 || $http >= 300) throw new RuntimeException('HTTP/network error');
$result = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
print_r($result); // Follow the response actions described above.

2. Read the discount

Make this request only after pending_redemption. Repeat while pending, with a finite waiting limit. On redemption_selected, stop polling, apply discount_amount once, and release checkout. On completed, continue without a discount. Stop on errors.

PHP · status request
<?php
$body = <<<'JSON'
{
  "order_id": "AD-45821",
  "branch_id": "IST-01",
  "option": "123123"
}
JSON;
$ch = curl_init('https://default.hooks.api.letstamp.com/v100/?q=status');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_TIMEOUT => 10,
  CURLOPT_HTTPHEADER => ['accessToken: YOUR_ACCESS_TOKEN', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => $body
]);
$raw = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw === false || $http < 200 || $http >= 300) throw new RuntimeException('HTTP/network error');
$result = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
print_r($result); // Follow the response actions described above.

3. Confirm after successful payment

Run this request only after a selected discount has been applied and the POS sale has successfully completed. Require status: true and check_status: "completed"; otherwise resolve the error before retrying.

PHP · checkout request
<?php
$body = <<<'JSON'
{
  "order_id": "AD-45821",
  "branch_id": "IST-01",
  "option": "123123"
}
JSON;
$ch = curl_init('https://default.hooks.api.letstamp.com/v100/?q=checkout');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_TIMEOUT => 10,
  CURLOPT_HTTPHEADER => ['accessToken: YOUR_ACCESS_TOKEN', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => $body
]);
$raw = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw === false || $http < 200 || $http >= 300) throw new RuntimeException('HTTP/network error');
$result = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
print_r($result); // Follow the response actions described above.

The examples use a 10-second request timeout as a client setting, not an API guarantee. Polling, applying the returned discount, and completing payment follow your existing POS flow, as described in Steps 3–5.

Advanced notes

Rewards, cashback, and customer classes

Letstamp owns selection and eligibility. Reward eligibility includes the check products and configured mappings; a positive cashback wallet can also trigger pending redemption. Availability is not a reservation guarantee.

Wallet redemption uses spendCashback, even when the app calls it Letstamp Points. The selected amount is limited by wallet balance and order total at selection time. JSON 5 and 5.00 are equivalent; fractional amounts such as 5.25 are supported. Use decimal-money handling without integer truncation.

Product mappings and reward benefits may differ by customer class. Letstamp preserves the reward's class context and uses it to resolve the applicable product mapping. Your POS does not calculate classes or send a class identifier.

Use the redemption and discount returned by Letstamp rather than recalculating the reward from the customer's current class. Class-specific mapping takes precedence when configured; otherwise the standard mapping is used.

Transport and routing

Use POST with a JSON body for all three operations. Use only the documented URLs: an unrecognized q value is treated as order submission.

Read status and check_status in the response body; HTTP status alone does not identify every API error. Also handle network failures and empty or non-JSON responses. Message text may be localized.

Confirm during onboarding
  • Sandbox availability and URL; rate limits and browser/CORS support.
  • Product-list authentication and refresh schedule.
  • Request sizes, field lengths, weighted quantities, tax, currency, and rounding policy.
  • Configured short-code lifetime and local polling deadline.
  • Recovery for expired codes, uncertain writes, payment failures, cancellations, and refunds.
Integration acceptance checklist

Use this list during your integration acceptance test.

Checklist selections are local to this page and are not saved or submitted.