← All guides

One merchant's own POS or software

How do I connect my own POS or back office to my card reader?

For
A merchant, or a developer working for one merchant.
Key
The merchant's own key (m360_live_…).
The same card-present calls as the ISV guide, with the merchant’s own key and no M360-On-Behalf-Of header.
1

Create the key

RequiredOnce

In Merchant360 → Developers, create a live key with only the scopes the software needs (for card readers: terminal:read, payments:read, payments:write). Keep it on a server or in protected settings, never in a web page or a shipped app.

In the API referenceAuthentication →Scopes →
2

Build against the sandbox

RequiredBefore go-live

A free m360_test_ key from the sandbox: any terminal_id works, the sale approves on GET /payments/{id}, and no money moves.

In the API referenceTest mode →
3

Find your readers

RequiredOnce, and when a reader changes

GET /terminal/devices lists the readers; save which terminal_id is at which station.

In the API referenceReaders (devices) →
4

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_live_…
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 →
5

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 →
6

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.
7

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 →
8

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.
9

Get results pushed to you

OptionalOnce

POST /webhooks with your HTTPS URL. Same benefits as for an ISV: results your POS missed, and refunds or voids done elsewhere.

In the API referenceWebhooks →
10

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 →