← All guides

A POS with no server (station keys)

How do I integrate when my POS calls Merchant360 straight from the store?

For
An ISV whose POS runs on the store PC with no server of its own in between.
Key
One station key (m360_stn_…) per register.
A platform key must never be shipped to stores. Instead each register gets a station key for its one reader at its one merchant. It runs that reader’s sales and nothing else, so a key copied off a store PC reaches one register.
POS station (station key)  ->  Merchant360 API  ->  that station's reader
result  <-  GET /payments/{id}
1

Get a station key for each register

Electronic Payments does thisOnce per register

Keys are made in the ISV portal: one at a time on the merchant’s page (Station keys, Mint key), or a file of keys for a whole rollout on the Developer tab. Your own portal logins can make them. Each key is shown or downloaded once; refunds are allowed only when ticked at that moment.

In the API referenceStation keys →
2

Install the key at its register only

RequiredOnce per register

Keep it in the POS’s protected settings on that register. No M360-On-Behalf-Of header: the key already names the merchant and the reader, and terminal_id may be left out. Check it with GET /me: it answers type: "station" and its terminal_id.

In the API referenceStation keys →
3

Send the sale to the reader

RequiredEvery sale

POST /payments with the amount in cents and the terminal_id of the reader at that station. Merchant360 hands the sale to that reader: it leaves its waiting screen, shows the amount and asks for the card (tap, chip or swipe). Card data never reaches your software.

Always send terminal_id: it is how Merchant360 knows which reader to wake. Left out, the sale goes to the merchant’s default reader, which is wrong for a store with two stations.

You get 202 at once, with status: "pending" and a payment id starting tp_. Save the id.

POST /payments
Authorization: Bearer m360_stn_…
Idempotency-Key: T-1001

{ "amount": 1800, "terminal_id": "PAXA35/2290000001", "invoice_number": "T-1001" }

202 { "id": "tp_5b0e…", "object": "payment", "status": "pending", "amount": 1800 }
In the API referenceSale →Payments →
4

Get the result

RequiredEvery sale

Ask for the payment every 2 to 3 seconds until its status is no longer pending:

  • succeeded: approved. amount is what was charged (with any tip or fee the merchant’s pricing adds on the reader); card has the brand and last 4.
  • failed: declined.
  • canceled: cancelled on the reader, or by you.

A sale is never cancelled on a timer. One still pending after 360 seconds is flagged for review and stays pending.

GET /payments/tp_5b0e…

200 { "id": "tp_5b0e…", "status": "succeeded", "amount": 1800,
      "card": { "brand": "VISA", "last4": "4242" } }
In the API referenceStatuses →
5

Cancel, void, refund, tip

RequiredWhen the clerk asks
  • POST /payments/{id}/cancel: stop a sale still waiting for the card. If the card was charged just before, it is voided.
  • POST /payments/{id}/void: undo an approved sale the same day, before the batch closes.
  • POST /payments/{id}/refund: money back after the batch closed. Send amount for part of it.
  • POST /payments/{id}/tip-adjust: add or change a tip on an approved sale.
6

Handle a lost answer

RequiredEvery POST

Send an Idempotency-Key (your order or ticket id) on every POST. If the answer is lost (timeout, restart, Wi-Fi drop), send the same POST again with the same key: you get the same payment back, never a second sale. Then read its status as in the previous step.

In the API referenceIdempotency →Recovery →
7

Per-sale options

OptionalAny sale
  • terminal_options.allow_cancel: false: no Cancel Sale button on the reader for this sale.
  • terminal_options.print_receipt: true: the reader prints or offers the receipt for this sale, for a register with no printer. false: your POS prints and the reader goes back to its waiting screen. Left out: the terminal’s setting (Payment App 2.46.4 or later).
  • terminal_options.allow_keyed: true: a Key enter card button, for a card that will not read. Needs keyed (MOTO) sales on the merchant; the reader then asks for CVV, ZIP and street number by default.
  • tip: { prompt: true }: the tip screen on the reader.
  • POST /payments/authorize with the same body: a hold instead of a sale, captured later with /payments/{id}/capture or released with /void. Needs pre-auth on the merchant.
8

Know what a station key cannot do

RequiredDesign

Another reader, customers, payment lists, reports, settings, webhooks and typed card numbers are all 403 station_key_scope. Refunds only when the key allows them. 120 requests a minute per key. Results come from GET /payments/{id}: a station key cannot register webhooks.

In the API referenceStation keys →
9

Show the error code

RequiredAny refused call

Every error is { "error": { "message", "code" } }. Most refusals name a setting, not an outage: show the code to your user instead of “contact support”.

  • 403 no_grant: the merchant is not connected to you.
  • 403 insufficient_scope: your key is not granted that kind of call.
  • 403 billing_tools_off: the merchant’s Billing Tools are off.
  • 403 not_permitted_by_channel: reports or refunds are off for partners on that merchant.
  • 409 terminal_unavailable: no card readers on the account.
  • 422 moto_not_enabled / 422 preauth_not_enabled: keyed sales / pre-auth are off for the merchant.
  • 429 rate_limited: too many requests in the minute.
  • 500: ours. Send us the time and your reference.
In the API referenceErrors →