Merchant login

Connect your POS & systems

Machine-to-machine access to JustIn WalkIn: publish deals from your till, Shopify store or any system by signed webhook, and flip inventory-driven flash deals live via the API. Hand this page to your developer, or to your AI agent.

The technical reference below is English-only, like code.

First: what's a POS?

Your point of saleis whatever rings up customers, a till app on a tablet (SumUp, Zettle, Lightspeed), a full register system, or your online shop's admin (Shopify, WooCommerce). It already knows things JustIn WalkIn can act on: what you sell, what it costs, what's about to expire, what's overstocked. This page is about connecting the two, so a decision made at the counter ("move these ten croissants before six") becomes a live, geofenced deal without anyone opening a dashboard.

The whole flow, in one picture

Your POS
till, Shopify or WooCommerce decides: publish, update, pause
Signed webhook
HMAC over the raw body: only your system can speak for you
Review & trust gate
new integrations queue for review; trusted ones go straight to live
Live on the map
the deal joins the feed, fences and the public deal page
Verified walk-in
a phone pings nearby, the counter scan is the only billable moment

Two integration surfaces

Deal webhookYour system creates deals on JustIn WalkIn, generic JSON or Shopify payloads, authenticated by an HMAC signature. Set up in Dashboard β†’ Account β†’ Integrations.
Flash APIYour system flips an existing, already-approved dealpaused⇄live with a stock count ("10 croissants left at 16:00"). Authenticated by a jwpos_… API key from the Account page.

Building with an AI agent? Start here

Paste this prompt into your coding agent (Claude Code, Cursor, Copilot Workspace, …) together with your webhook URL and secret, it contains the complete contract: endpoint, signature scheme with a verifiable self-test vector, every field with its units, all response shapes and the retry semantics. The human-readable reference below covers the same ground.

You are integrating a POS / e-commerce / inventory system with JustIn WalkIn, a location-based deals platform. Implement an outbound webhook that publishes deals. Everything you need is below; do not invent endpoints or fields.

CREDENTIALS (ask the merchant; from their JustIn WalkIn dashboard -> Account -> Integrations):
- WEBHOOK_URL   (already contains ?integration=<uuid>)
- WEBHOOK_SECRET (hex string, shown once at creation)

REQUEST
POST {WEBHOOK_URL}
Content-Type: application/json
X-JW-Signature: lowercase hex HMAC-SHA256 of the RAW request body bytes, keyed with WEBHOOK_SECRET.
Sign exactly the bytes you transmit - no re-serialisation, no added whitespace, no trailing newline.

BODY (JSON object)
required:
  reference_id  string  your stable id for the deal; the idempotency key
  title         string  consumer-facing deal name
optional:
  discount_pct      int 1-99 (default 10)
  base_price_minor  int, regular price in MINOR units (CHF 6.50 -> 650; GBP/EUR same).
                    EXCEPTION: HUF is whole forint (1500 Ft -> 1500, NOT 150000).
                    Omitted -> the integration's configured "typical basket value" is used.
  description       string
  category          one of: restaurant bakery coffee bar groceries beauty fitness fashion pharmacy entertainment services retail
  location_id       uuid of one of the merchant's own locations (omitted -> integration default)
  expires_at        ISO 8601 timestamp; the deal closes itself then
Never send a currency - it always comes from the shop's account settings.

RESPONSES
201 {"offer_id":"<uuid>","status":"pending"|"live","created":true}
    Created. A NEW integration always gets "pending" (human review queue). Once the platform
    marks the integration trusted, deals go "live" directly - unless the shop's live-deal
    ceiling is full, then "pending". Do not treat "pending" as an error.
200 same shape with "created":false
    This reference_id was already ingested. Idempotent replay - retries are safe and expected.
200 {"offer_id":null,"status":"rejected_unclean","created":false}
    Title/description failed the platform word filter. Not published, merchant notified.
    Do NOT retry unchanged.
401 bad/missing signature, or unknown/paused integration.
422 missing reference_id/title, or a location_id the merchant does not own.

SELF-TEST YOUR HMAC before going live:
secret  c0ffee00c0ffee00c0ffee00c0ffee00c0ffee00c0ffee00
body    {"reference_id":"pos-1042","title":"Morning espresso -20%","discount_pct":20}
must produce
        3e35efc2650aeccc322c31a223e8421a062b3a340e771eed649444c467a3657f

LIFECYCLE (optional "action" field; default "create")
- {"action":"update","reference_id":..., any of: title, description, discount_pct,
  base_price_minor, expires_at} -> {"result":"updated"|"rejected_unclean"|"not_found"}.
  Only sent fields change; the deal's live/pending status is untouched; the ~3-month
  cap re-applies from now.
- {"action":"delete","reference_id":...} -> closes the deal ({"result":"closed"}).
  Deleting a never-imported ref returns {"result":"not_found"} - not an error.
- {"action":"flash","live":true|false, "reference_id" or "offer_id", optional
  "stock" (re-bases the remaining counter), optional "hours" (1-168 self-closing
  window)} -> pause/resume an APPROVED deal on inventory signals.
  Results: ok | not_found | not_controllable (deal not live/paused) |
  live_limit (shop's live ceiling full) | expired_needs_hours.

RULES
- One deal = one reference_id, forever. Retry the SAME id freely; never mint a new id for a retry.
- Money is integers only. Respect the HUF whole-forint exception.
- Keep WEBHOOK_SECRET server-side only; rotate it from the dashboard if it leaks.

1 Β· Deal webhook (generic)

Create an integration in Dashboard β†’ Account β†’ Integrations. You get a webhook URL (it carries your integration id) and a secret shown exactly once. Send an HTTP POST with a JSON body to the URL.

Endpoint

POST https://xvgwwgaubpkghxxdanhp.supabase.co/functions/v1/pos-webhook-receiver?integration=<your-integration-id>
Content-Type: application/json
X-JW-Signature: <hex HMAC-SHA256 of the RAW request body, keyed with your secret>

Authentication, the signature

Sign the raw request body bytes, exactly as sent, before any parsing, re-serialisation or whitespace changes, with HMAC-SHA256 using your webhook secret as the key, and send the digest hex-encoded (lowercase) in the X-JW-Signature header. Comparison is constant-time server-side; a bad signature returns 401 and nothing is processed.

Worked example you can verify, with the (fake) secret c0ffee00c0ffee00c0ffee00c0ffee00c0ffee00c0ffee00 and this exact body:

{"reference_id":"pos-1042","title":"Morning espresso -20%","discount_pct":20}

the signature is:

3e35efc2650aeccc322c31a223e8421a062b3a340e771eed649444c467a3657f
# bash / any POS backend
BODY='{"reference_id":"pos-1042","title":"Morning espresso -20%","discount_pct":20}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -X POST "$WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -H "X-JW-Signature: $SIG" \
  --data-binary "$BODY"

Fields

FieldTypeNotes
reference_idstring, requiredYour stable id for this deal. Idempotency key: retries and duplicates with the same id return the original deal instead of creating another.
titlestring, requiredThe deal name shown to consumers. Runs through the platform word filter, a blocked title is rejected (never published) and the shop owner gets an inbox notice.
discount_pctinteger 1–99Percentage off. Defaults to 10 when omitted.
base_price_minorinteger, optionalRegular price in minor units (CHF 6.50 β†’ 650; GBP/EUR the same). Exception: HUF is whole forint, 1500 Ft β†’ 1500, not 150000. Omitted β†’ the integration's "typical basket value" is used. Currency itself always comes from the shop's account setting.
descriptionstring, optionalLonger text under the title. Same word-filter rule.
categoryenum, optionalOne of restaurant bakery coffee bar groceries beauty fitness fashion pharmacy entertainment services retail. Unknown values fall back to the integration's default.
location_iduuid, optionalMust belong to your shop; omitted β†’ the integration's default location.
expires_atISO 8601, optionalThe deal closes itself at this moment.

Responses

201{"offer_id":"…","status":"pending"|"live","created":true}, deal created. See the lifecycle below for which status you get.
200Same shape with "created":false, this reference_id already exists (idempotent replay; safe to retry webhooks freely).
200 + rejected_unclean{"offer_id":null,"status":"rejected_unclean","created":false} β€” the text failed the word filter; nothing was published, the owner was notified.
401Bad or missing signature, or unknown/paused integration.
422Parseable JSON but missing reference_id/title, or the deal could not be ingested (e.g. a location that isn't yours).

Lifecycle, why your first deals say "pending"

Deals from a new integration land in the normal review queue (status: pending) like any hand-written deal. Once the platform has reviewed your integration and marked it trusted, subsequent webhooks publish straight to live, within your shop's live-deal ceiling; when the ceiling is full the deal degrades to pendinginstead of failing. Trust is revocable. The Integrations tab shows which state you're in.

Updating, closing and inventory-driven flashing

Add an action field to the same signed POST: update changes only the fields you send (same word-filter rule; status untouched; the ~3-month cap re-applies), delete closes the deal, and flash pauses/resumes an approved deal on inventory signals β€” {"action":"flash","reference_id":"…","live":true,"stock":10,"hours":3} re-bases the remaining counter and sets a self-closing window. Unknown references return not_found as data, never an error, so retries and cleanups are always safe. A full live roster returns live_limit instead of failing.

2 Β· Shopify

Same receiver, Shopify's own signing scheme (X-Shopify-Hmac-Sha256, base64. Shopify computes it for you). Setup:

  1. In Dashboard β†’ Account β†’ Integrations, choose Shopify and enter your store URL (my-shop.myshopify.com), an Admin API access token (from a custom app in your Shopify admin), and your shop's webhook signing secret.
  2. In Shopify admin β†’ Settings β†’ Notifications β†’ Webhooks: create webhooks for Price rule creation, and, for the full lifecycle, Price rule update and Price rule deletion, all pointing at your webhook URL from step 1 (the topic header routes them). The signing secret is displayed on that same Shopify page.

Mapping: the price rule's title becomes the deal name; value_type: percentage maps directly to the discount; value_type: fixed_amountis converted to an effective percentage of your integration's typical basket value (Shopify payloads carry no base price); ends_at becomes the expiry; the price rule's id is the idempotency reference.

2b Β· WooCommerce

Native support for coupon webhooks: in WooCommerce β†’ Settings β†’ Advanced β†’ Webhooks, create webhooks for Coupon created / updated / deleted, set the delivery URL to your webhook URL, and invent a secret, enter the same secret when connecting WooCommerce in the Integrations tab (Woo signs with X-WC-Webhook-Signature, base64). Mapping: the coupon code is the deal name, percent coupons map directly, fixed_cart/fixed_product convert to an effective percentage of your typical basket value, date_expiresbecomes the deal expiry. Woo's unsigned activation ping is answered automatically.

SumUp, Zettle, Lightspeed & other tills:these systems don't emit deal/discount webhooks natively, connect them through the generic webhook above via their automation partners (Zapier/Make/n8n) or a small script; the AI-agent prompt writes that script for you.

3 Β· Flash API (inventory β†’ pause⇄live)

For deals that already exist and were approved: flip them live when stock appears, pause them when it runs out. Auth is a per-shop API key (jwpos_…) managed on the Account page, no signature needed here, treat the key like a password.

curl -X POST https://justinwalkin.com/api/pos/flash \
  -H "Authorization: Bearer jwpos_…" \
  -H "Content-Type: application/json" \
  -d '{"offer_id":"<uuid>","live":true,"stock":10,"hours":3}'
offer_iduuid, required, an approved deal of yours
liveboolean, required, true publishes (within your live-deal ceiling), false pauses
stockinteger, optional, re-bases the remaining-redemptions counter ("10 left")
hoursinteger, optional, self-closing flash window from now (1–48)

Ground rules (apply to every surface)

  • Money is integer minor units + the shop's currency, never floats, never a currency you don't trade in. HUF is whole forint.
  • Retries are safe everywhere: webhooks are idempotent on reference_id; the flash API is a state set, not an increment.
  • Text is moderated: every title/description passes the platform word filter; failures never publish.
  • Credentials are one-way: secrets are shown once at creation and are not retrievable afterwards, rotate from the Integrations tab if lost.
  • Redemption itself never moves through this API, codes are minted and burned only by the consumer/employee flow, so an integration can never spend a customer's deal.
  • Terms of use:API credentials are issued per merchant, for that merchant's own shops only, no sharing, resale or use on behalf of third parties; no scraping, load testing or reimplementation of the platform through this surface. Abuse leads to integration suspension. Deals published via the API are subject to the same review, content and fee rules as any other deal.

Credentials live in your merchant dashboard: Account β†’ Integrations (webhook URL + secret) and Account (flash API key).

Open the merchant dashboard
Got a question? Ask us.AI answers, informational, not binding.