Skip to navigation

Import webshop sales into the POS

Register finished external sales so a customer can return the goods in any store.
View as Markdown

A customer buys online and wants to return the item to a shop. The shop assistant scans the receipt, and the till has never heard of the sale.

Register Sale fixes that. It imports finished sales from external systems (webshops, self-checkout solutions) into ViKING POS, so the transaction exists in the estate and stores can act on it.

“Finished” is the operative word. This API is for sales that have already completed and been paid for elsewhere. To price a basket before payment, use Basket API.

Two endpoints

POST /v1/sale submit a sale, get a correlation ID
GET /v1/sale/{correlationId} check its processing status

The base URL is https://api.fiftytwo.com/register-sale-api.

Register Sale authenticates with an Auth0-issued Bearer JWT, not a token from auth.fiftytwo.com. The header format is identical; the issuer is not. Ask your FiftyTwo consultant which tenant issues yours.

The flow

Processing is asynchronous. Submitting a sale returns immediately with a correlation ID; the import happens after.

1

Submit the sale

curl -X POST https://api.fiftytwo.com/register-sale-api/v1/sale \
-H "Authorization: Bearer $AUTH0_TOKEN" \
-H "Content-Type: application/json" \
-d @sale.json

You get a correlation ID back. The sale is now Pending.

2

Poll for a terminal status

curl "https://api.fiftytwo.com/register-sale-api/v1/sale/$CORRELATION_ID" \
-H "Authorization: Bearer $AUTH0_TOKEN"

A sale transitions from Pending to Processed or Failed. Those two are terminal, so stop polling when you see either.

3

Handle Failed explicitly

Failed is a real outcome, not a transport error. Something about the sale was rejected. Do not retry blindly; surface it.

Idempotency is your job

If a POST /v1/sale times out, you do not know whether the sale was accepted. Retrying can import it twice, and a duplicate sale means a customer could return the same goods twice.

Persist the correlation ID before you consider the submission done, and key your retry logic on your own order ID:

import requests
BASE = "https://api.fiftytwo.com/register-sale-api"
def register_sale(order, token, store):
"""Submit a sale at most once per order, even across retries."""
existing = store.get_correlation_id(order.id)
if existing:
return existing # already submitted; just poll it
resp = requests.post(
f"{BASE}/v1/sale",
headers={"Authorization": f"Bearer {token}"},
json=order.to_payload(),
timeout=30,
)
resp.raise_for_status()
correlation_id = resp.json()["correlationId"]
# Record before returning: a crash here is recoverable, a lost ID is not.
store.save_correlation_id(order.id, correlation_id)
return correlation_id

If the POST times out with no ID recorded, reconcile before resubmitting, ideally by checking whether the sale already exists via Transaction POS Log.

Poll with backoff

import time
TERMINAL = {"Processed", "Failed"}
def wait_for(correlation_id, token, timeout=300):
deadline = time.monotonic() + timeout
delay = 1.0
while time.monotonic() < deadline:
resp = requests.get(
f"{BASE}/v1/sale/{correlation_id}",
headers={"Authorization": f"Bearer {token}"},
timeout=15,
)
resp.raise_for_status()
status = resp.json()["status"]
if status in TERMINAL:
return status
time.sleep(delay)
delay = min(delay * 1.6, 15.0)
raise TimeoutError(f"{correlation_id} still pending after {timeout}s")

Poll from a background worker, not from the request that completes the customer’s checkout. The customer should not wait on POS import.

What the store sees

Once processed, the sale is a transaction in the estate like any other. A shop assistant can find it and process a return against it through the normal returns flow. See Returns and corrections and Handle returns.

Choosing between this and the Receipt API

The Receipt API also accepted posted receipts from external systems, and it is deprecated. New integrations should use Register Sale for writing sales in, and Transaction POS Log for reading transactions out.