SimplyClub

API Without the Widget

api rest auth otp backend no-widget

Use Simply Club from your own UI — your own login screen, app, or backend — no embedded JavaScript widget required. Everything the Drop-in does over the wire is a plain REST call you can make yourself: log a shopper in with phone + OTP, get a session token, and read their member profile and benefits.

In short — Logging a shopper in is two requests: the first texts them a one-time code, the second sends that code back and returns a session token. You attach that token to every later request to act as that shopper. That’s the whole integration — the exact URLs are in the steps below.


Building your own UI

This is the path for when you want your own login screen — a React component, a Vue page, a native mobile screen — instead of our widget. You draw the UI; you call these endpoints behind it. In practice it’s two screens:

  1. Phone screen — a phone input + a “Send code” button → calls get-otp (Step 1 below).
  2. Code screen — a code input + a “Verify” button → calls validate-otp (Step 2 below). On success you hold the access token and the shopper is logged in.

A few things that make this safe and easy from the front end:

  • In a browser it just works. If your page is served from an allow-listed domain, the browser sends the Origin header for you and CORS is already open to that domain — no extra config.
  • There are no API keys or secrets in this flow, so it’s safe to run entirely client-side. The shopper’s OTP is their credential.
  • Store the access token like any session token (e.g. localStorage or a cookie) and send it as Authorization: Bearer on later calls.

The rest of this page is the exact calls those two screens make.


The full cycle

Login is just the first box. A complete rewarded checkout — the same thing the widget does — is five steps. Every call after login carries your Authorization: Bearer <access> token (and the Origin header). The boxes flow top to bottom; each one names the endpoint and what you get back:

┌──────────────────────────────────────────────┐
│  1. LOG IN                                     │
│  POST /users/fast/get-otp/:poiId/:phone        │
│  POST /users/fast/validate-otp/:poiId          │
│  ⇒ access token (90 days)                      │
└───────────────────────┬────────────────────────┘
                        │  send the cart

┌──────────────────────────────────────────────┐
│  2. START A TRANSACTION                        │
│  POST /transaction/create/:poiId               │
│  body: { items: [...] }                        │
│  ⇒ available promos + benefits + totals        │
└───────────────────────┬────────────────────────┘
                        │  shopper picks benefits in your UI

┌──────────────────────────────────────────────┐
│  3. LOCK THE SELECTION                         │
│  POST /transaction/select-and-lock-benefits/   │
│       :poiId                                   │
│  ⇒ confirmed discount for this cart            │
└───────────────────────┬────────────────────────┘


┌──────────────────────────────────────────────┐
│  4. GET THE TRANSACTION CODE                   │
│  GET /transaction/get-transaction-code/:poiId  │
│  ⇒ { code }  →  the sc_transaction_code.       │
│     Save it on your order.                     │
└───────────────────────┬────────────────────────┘
                        │  shopper pays on your checkout

┌──────────────────────────────────────────────┐
│  5. AWARD POINTS  (from your backend)          │
│  POST /transaction/order-webhook               │
│  body: order details + sc_transaction_code     │
│  ⇒ points awarded, transaction closed          │
└──────────────────────────────────────────────┘
StepEndpointWhat it’s forDocumented in
1fast/get-otp + fast/validate-otpLog the shopper in, get a session token.§3 below
2POST /api/v1/transaction/create/:poiIdSend the cart; get the promos/benefits the shopper qualifies for.§5.1 below
3POST /api/v1/transaction/select-and-lock-benefits/:poiIdLock the benefits the shopper chose.§5.2 below
4GET /api/v1/transaction/get-transaction-code/:poiIdGet the sc_transaction_code to attach to your order.§5.3 below
5POST /api/v1/transaction/order-webhookAfter payment, award points and close the transaction.Webhook Integration Guide

Steps 2–4 are the part the widget normally runs for you via its benefits engine — full detail in §5 (Run a rewarded checkout). They all take the same session Bearer token from step 1, so you can drive the entire flow from your own client. Step 5 is fired by your backend after the shopper pays — without it the shopper sees the discount but no points are awarded.


1. What you need

#RequirementHow to get it
1A poiIdFrom your Simply Club account manager — an opaque id like 6821f55303f86ff040461ef4.
2Fast login enabled for that POIYou control this yourself — sign in to the Dashboard as the POI admin and flip the fast login toggle in your POI’s Settings. (It sets settings.fastLoginEnabled on the POI; without it the fast routes return 403.)
3Your calling domain on the POI’s allowed listIf the POI restricts domains, give support the hostname(s) you’ll send as the Origin header. See the Origin rule below.

There is nothing to install. You’re just calling HTTPS endpoints.

Base URL

EnvironmentBASE_URL
Productionhttps://dropins.simplyclub.co.il
Staginghttps://simply-drop-ins-2.onrender.com

Enabling fast login yourself

You don’t need to ask anyone — a POI admin turns it on. The easiest way is the Dashboard: sign in, open your POI’s Settings, and toggle fast login on.

If you’d rather automate it, it’s two API calls:

# 1) Log in as the POI admin (env = test | prod)
POST {BASE_URL}/api/v1/poi/login/:env
Content-Type: application/json

{ "accountId": 2886, "username": "your-admin-user", "password": "your-password" }

The response includes your poiId and an admin token:

{ "success": true, "data": { "token": { "token": "<admin-token>" }, "poi": { "_id": "<poiId>" } } }
# 2) Flip the flag — only this field changes, every other setting is left intact
PATCH {BASE_URL}/api/v1/poi/:poiId/settings
Authorization: Bearer <admin-token>
Content-Type: application/json

{ "fastLoginEnabled": true }

That’s it — fast login is now live for that POI.


2. The one rule: send an Origin header

Simply Club scopes a POI to a set of allowed domains. If your POI has domains configured, every request — OTP and authenticated alike — must include an Origin (or Referer) header whose hostname is on that list. Browsers add this automatically; a backend HTTP client does not, so you must set it yourself:

Origin: https://your-allowed-domain.com
  • If the POI has no domain restriction (or it’s set to *), you can skip the header.
  • If it’s restricted and you omit it, you get 403 with ORIGIN_REQUIRED.
  • If you send a hostname that isn’t allowed, you get 403 with DOMAIN_NOT_ALLOWED.

Use the same Origin value on the login call and on every call afterward — the session token is bound to it.


3. Log a shopper in (two calls)

Step 1 — Request an OTP

POST {BASE_URL}/api/v1/users/fast/get-otp/:poiId/:phone
Origin: https://your-allowed-domain.com

The server texts a code to the phone and returns an otpChallenge — a one-time token you must echo back in step 2. The code is valid for 120 seconds.

200 response:

{
  "success": true,
  "data": {
    "message": "OTP sent",
    "otpChallenge": "<64-hex-char string>",
    "isOTPSent": true
  },
  "error": null
}

Step 2 — Validate the OTP and get a token

POST {BASE_URL}/api/v1/users/fast/validate-otp/:poiId
Origin: https://your-allowed-domain.com
Content-Type: application/json

{
  "otp": "123456",
  "otpChallenge": "<the otpChallenge from step 1>"
}

200 response:

{
  "success": true,
  "data": {
    "message": "OTP is valid",
    "access": "<session token>",
    "member": { /* the Simply Club member */ },
    "isNewUser": false
  },
  "error": null
}

access is the session token. Store it — it’s valid for 90 days. isNewUser is true only when the member was just created (a new phone auto-creates a member).


4. Use the token

Put the token in an Authorization: Bearer header (keep the same Origin header too). These are the endpoints you’ll typically call:

Method & pathReturns
GET /api/v1/users/my-self/:poiIdThe logged-in member’s profile.
GET /api/v1/users/user-benefits/:poiIdThe member’s available benefits.
POST /api/v1/users/ping/:poiId`{ authenticated: true
POST /api/v1/users/logoutEnds the session (invalidates the token).

Example:

GET {BASE_URL}/api/v1/users/my-self/:poiId
Authorization: Bearer <access>
Origin: https://your-allowed-domain.com

5. Run a rewarded checkout

These are the three calls between login and the order webhook (steps 2–4 of the full cycle). All of them require Authorization: Bearer <access> plus the Origin header, and they operate on the shopper’s open transaction.

The field names selectedQuntity and maxQuntityAllowed are spelled exactly like that in the API — match them verbatim.

5.1 Create a transaction (send the cart)

POST {BASE_URL}/api/v1/transaction/create/:poiId
Authorization: Bearer <access>
Origin: https://your-allowed-domain.com
Content-Type: application/json

{
  "items": [
    { "id": "1", "sku": "ABC-001", "title": "Olive Oil 500ml", "price": 39.9, "quantity": 2 }
  ]
}

price is the net unit price after any store-side discounts.

200 response — the benefits the shopper qualifies for:

{
  "success": true,
  "data": {
    "message": "Transaction created",
    "serviceTranNumber": "<transaction id>",
    "tranDiscountPercent": 0,
    "promos": [
      {
        "id": 12345,
        "name": "10% off oils",
        "description": "...",
        "templateId": 0,
        "maxQuntityAllowed": 1,
        "unitPrice": 7.98,
        "selectedQuntity": 0,
        "isAuto": false
      }
    ]
  }
}

Each entry in promos is a benefit the shopper can apply. templateId is 0 for a total discount, 1 for cashback; selectedQuntity starts at 0.

5.2 Lock the chosen benefits

Send back the promos the shopper picked — with selectedQuntity set to how many they want — along with the same cart items:

POST {BASE_URL}/api/v1/transaction/select-and-lock-benefits/:poiId
Authorization: Bearer <access>
Origin: https://your-allowed-domain.com
Content-Type: application/json

{
  "items": [
    { "id": "1", "sku": "ABC-001", "title": "Olive Oil 500ml", "price": 39.9, "quantity": 2 }
  ],
  "promos": [
    { "id": 12345, "templateId": 0, "unitPrice": 7.98, "selectedQuntity": 1 }
  ]
}

200 response: { "success": true, "data": { "message": "Benefits locked" } } (or "Tran check completed", depending on POI config) — either confirms the discount for this cart.

This needs the open transaction from 5.1. If there’s no session yet you get 400 with Session not found.

5.3 Get the transaction code

GET {BASE_URL}/api/v1/transaction/get-transaction-code/:poiId
Authorization: Bearer <access>
Origin: https://your-allowed-domain.com

200 response:

{ "success": true, "data": { "code": "<sc_transaction_code>" } }

Save code (the sc_transaction_code) on your order — your backend sends it in the webhook below to award points.

5.4 Award points after payment (backend)

Once the shopper pays on your checkout, your backend POSTs the order — including the sc_transaction_code from 5.3 — to close the transaction and award points:

POST {BASE_URL}/api/v1/transaction/order-webhook

The full payload (topic, domain, sc_transaction_code, order_id, customer_id) and signature details are in the Webhook Integration Guide. Without this call the shopper sees the discount but no points are awarded.


6. Full walkthrough with curl (login → checkout)

The whole cycle as one runnable script. Paste the otpChallenge from call 1 into call 2, then the access token from call 2 into ACCESS:

BASE_URL="https://dropins.simplyclub.co.il"
POI_ID="your_poi_id"
PHONE="+972501234567"
ORIGIN="https://your-allowed-domain.com"

# 1) Send the OTP — copy the otpChallenge from the response
curl -s -X POST "$BASE_URL/api/v1/users/fast/get-otp/$POI_ID/$PHONE" \
  -H "Origin: $ORIGIN"

# 2) Validate it — returns { data: { access, member } }
curl -s -X POST "$BASE_URL/api/v1/users/fast/validate-otp/$POI_ID" \
  -H "Origin: $ORIGIN" \
  -H "Content-Type: application/json" \
  -d '{ "otp": "123456", "otpChallenge": "PASTE_CHALLENGE_HERE" }'

# Reuse the session token for every call below
ACCESS="PASTE_ACCESS_TOKEN_HERE"

# 3) Create a transaction with the cart — returns { data: { promos: [...] } }
curl -s -X POST "$BASE_URL/api/v1/transaction/create/$POI_ID" \
  -H "Origin: $ORIGIN" -H "Authorization: Bearer $ACCESS" \
  -H "Content-Type: application/json" \
  -d '{ "items": [ { "id": "1", "sku": "ABC-001", "title": "Olive Oil 500ml", "price": 39.9, "quantity": 2 } ] }'

# 4) Lock the benefits the shopper picked (set selectedQuntity)
curl -s -X POST "$BASE_URL/api/v1/transaction/select-and-lock-benefits/$POI_ID" \
  -H "Origin: $ORIGIN" -H "Authorization: Bearer $ACCESS" \
  -H "Content-Type: application/json" \
  -d '{ "items": [ { "id": "1", "sku": "ABC-001", "title": "Olive Oil 500ml", "price": 39.9, "quantity": 2 } ], "promos": [ { "id": 12345, "templateId": 0, "unitPrice": 7.98, "selectedQuntity": 1 } ] }'

# 5) Get the transaction code — save data.code on your order
curl -s "$BASE_URL/api/v1/transaction/get-transaction-code/$POI_ID" \
  -H "Origin: $ORIGIN" -H "Authorization: Bearer $ACCESS"

# 6) After the shopper pays, your BACKEND fires the order webhook with that
#    code to award points — see §5.4 and the Webhook Integration Guide.

7. When something fails

Every error uses the same envelope — success: false with an errorCode, plus friendlyMessage / friendlyMessageHebrew you can show a shopper. The common ones:

HTTPerrorCodeMeaning
400OTP_REQUIREDNo code in the validate body.
404OTP_INVALIDWrong or unknown code.
400OTP_EXPIREDCode older than 120 seconds — request a new one.
403OTP_CHALLENGE_MISMATCHotpChallenge doesn’t match the one from step 1.
401(auth)Missing/expired Authorization token on an authenticated call.
403ORIGIN_REQUIRED / DOMAIN_NOT_ALLOWEDOrigin header missing or not on the POI’s allowed list.
403(POI gate)fastLoginEnabled is off for this POI.

The complete error table and security details live in Fast Login.


If you want to…Read
Skip the OTP screen — your backend already knows the shopperLogin From Backend
Embed the full UI instead of calling the API yourselfGetting Started
Understand the phone-only fast-login channel in depthFast Login