Skip to main content

POS Terminal API suite

Drive a SquadPOS terminal directly from your custom application: read its configuration, load its keys, take card payments, and perform reconcilliation — using plain JSON.​

Squad provides a secure fully encrypted and credentialed channel between your POS terminal and Squad's backend services, requiring no further implementation from your end. This service holds the terminal's keys, applies the encryption on your behalf, and forwards the request. You send and receive a JSON request, authenticated with your default Squad credentials retrieved from your Squad account.

Environmental Setup

Base URL: https://api-d.squadco.com/softpos/

Authorization keys are to be passed via Headers as a Bearer token.

Example: Authorization: Bearer sandbox_sk_94f2b798466408ef4d19e848ee1a4d1a3e93f104046f.

Terminal serial number: Printed on the device and readable from its SDK. This identifies the physical terminal.

A way to read a card: The terminal's own SDK. You supply the card data.

info

Provided by Squad: Terminal credentials, POS encryption, message assembly, and talking to the card processor.

Provided by the Merchant: Reading the card via the device SDK, capturing the PIN on the device's secure keypad, and building the PIN block.

info

No POS key material is issued except the terminal PIN key.

Response envelope: Every response shares the same envelope:

{
"status": 200,
"success": true,
"message": "success",
"data":{


}
}

Everything documented under "Response" below is the contents of data.

Sequence​

The order in which API calls are made is critical. The PIN block cannot be generated without the terminal key, and most card transactions will not be successfully authorised without a valid PIN block.

once per app start
┌───────────────────────────────┐
│ 1. Get Terminal Configuration │ confirm the terminal, get its identity
└───────────────┬───────────────┘
│
┌───────────────▼───────────────┐
│ 2. Key Exchange │ get pin_key — keep it for the session
└───────────────┬───────────────┘
│
per payment │
┌───────────────▼───────────────┐
│ read card + PIN on device │ your SDK
│ build pin_block │ from the PIN, the PAN and pin_key
└───────────────┬───────────────┘
│
┌───────────────▼───────────────┐
│ 3. Initiate Transaction │ plain JSON — encrypted and forwarded
└───────────────┬───────────────┘
│
if the response never arrives or any form disruption
┌───────────────▼───────────────┐
│ 4. Find by Merchant Reference │ requery to confirm status
└───────────────────────────────┘

Get Terminal Configuration​

Confirms the terminal exists, belongs to you as the merchant, and is active. Call it once at startup, or whenever you point your app at a different terminal.

GET
https://api-d.squadco.com/softpos/terminal/{serial_no}/config

This endpoint retrieves all terminal configuration

Parameters

Header

Authorization*

String

API keys (Secret Key) that authorize your transactions and gotten from your squad dashboard

Body

serial_no*

String

The terminal's serial number. Read it from the device SDK rather than hard-coding it — it is the only thing identifying the physical terminal.

Responses

200: Terminal Found
Successful
{
"status": 200,
"success": true,
"message": "Success",
"data": {
"terminal": {
"terminal_id": "2058R1KK",
"serial_no": "P332600026040",
"terminal_type": "pos",
"status": "active",
"currency": "NGN",
"mcc_code": "5511",
"address": {
"street": "171 moshood olugbani",
"city": null,
"region": "Ekiti State",
"country_code": "NG"
},
"latitude": "6.426315",
"longitude": "3.438079"
},
"merchant": {
"merchant_id": "ZB5BCG3Y",
"name": "Marvil Graphics PO Test"
},
"logo": {
"key": "ZB5BCG3Y/logo/370/1789660786759-ff8e9aa1.jpg",
"url": "https://d3tcu8z9v5kjyt.cloudfront.net/ZB5BCG3Y/logo/370/1789660786759-ff8e9aa1.jpg",
"file_name": "moneymaster logo 2.jpg",
"ext": "jpg",
"verified_at": "2026-09-17T15:59:49.243Z"
}
}
}

400: Non-existent Serial No
Invalid Serial no
{
"status": 400,
"success": false,
"message": "Incorrect device information",
"data": {}
}
404:Terminal Not Found
Not Found
{
"status": 404,
"success": false,
"message": "Terminal not found",
"data": {}
}

Key Exchange​

This mandatory endpoint retrieves the terminal’s working keys from the card processor. The most critical value in the response is the pin_key, which is used to encrypt the cardholder’s PIN and generate the pin_block that is submitted with the payment transaction.

GET
https://api-d.squadco.com/softpos/terminal/{serial_no}/key-exchange

This endpoint returns terminal working keys

Parameters

Header

Authorization*

String

API keys (Secret Key) that authorize your transactions and gotten from your squad dashboard

Query Parameters

refresh

Boolean

Force a fresh exchange with the processor instead of reusing cached keys. Slower and can fail if the processor is unreachable. Only use it if you believe the keys are stale.

'

warning

Most Nigerian cards require an online PIN. Two things follow:

  1. The card will not produce a cryptogram at all unless a PIN was entered — so without one you get no usable chipdata.
  2. The processor will not authorise without a valid pin_block — and you cannot build one without pin_key.

Skipping this step produces a transaction that is declined or simply never answered, with nothing in the response explaining why.

Responses

200 Success
Success
{
"status": 200,
"success": true,
"message": "Success",
"data": {
"terminal_id": "2058R1KK",
"pin_key": "2F1049E623D976313467E9295762EC6E",
"master_key": "E9CB76B57FB649F7DAC4578F0D150783",
"session_key": null,
"working_key": "AC706424899E4647C660BB56CBBF84B0",
"response_code": "00",
"response_message": "Successful"
}
}

200 Key Exchange with Refresh
Success
{
"status": 200,
"success": true,
"message": "Success",
"data": {
"terminal_id": "2058R1KK",
"pin_key": "3802758FC87ADA2F985E3D13C8081F26",
"master_key": "FBA7CEE3130B588967E3D58C57B620DC",
"session_key": null,
"working_key": "31C2FA927E739D220E003C40FFA698FA",
"response_code": "00",
"response_message": "Successful"
}
}
400 Non-existent Serial No
Invalid serial no
{
"status": 400,
"success": false,
"message": "Incorrect device information",
"data": {}
}
404 Terminal Not Found
Not Found
{
"status": 404,
"success": false,
"message": "Terminal not found",
"data": {}
}

'

FETCH ONCE, KEEP IT
A physical terminal loads this key during configuration and injects it into its PIN pad
hardware. Do the same: fetch at startup, hold it for the session, re-fetch on restart.
Fetching per transaction adds a round trip to a path that is already time-sensitive.

HANDLING PIN_KEY SAFELY
On a real terminal this key lives in tamper-resistant hardware and never reaches
application memory. Over an API it necessarily lands in your process.

Never log it.
Never persist it in plain text.
Never send it to a third party.
Keep it in memory only.
Treat a leak of this key as you would a leak of card data.

Example

{
"status": 200,
"success": true,
"message": "Success",
"data": {
"terminal_id": "2058WDSG",
"pin_key": "<32 hex characters>",
"master_key": "<32 hex characters>",
"session_key": null,
"working_key": "<32 hex characters>",
"response_code": "00",
"response_message": "Successful"
}
}

Initiate Transaction​

This endpoint charges the card directly. Once the plain JSON request is sent, Squad applies the terminal credentials and POS encryption and forwards it to the processor. Every field except amount, account_type and merchant_ref comes directly from the card read.

GET
https://api-d.squadco.com/softpos/terminal/{serial_no}/transaction

This endpoint charges the card

Parameters

Header

Authorization*

String

API keys (Secret Key) that authorize your transactions and gotten from your squad dashboard

Body

amount*

String

The amount you are debiting customer (expressed in the lowest currency value - kobo& cent). 10000 = 100NGN for Naira Transactions

currency

String

Defaults to NGN

card_pan*

String

Card number, digits only. EMV tag 5A, or the kernel's card-number callback.

card_expiry_month*

String

Two digits, 01–12. Taken from track 2.

card_expiry_year*

String

Two digits, e.g. 28. Taken from track 2.

card_type

String

Scheme name — Verve, MasterCard, VISA. Derive it from the AID.

cardholder_name

String

EMV tag 5F20, hex decoded to ASCII.

card_sequence_number*

String

Exactly three digits.

track2*

String

Track 2 equivalent data, EMV tag 57, tag and length stripped. A trailing F pad nibble is acceptable.

chipdata*

String

The DE55 blob — the card's cryptogram and terminal data, hex. Read after the kernel asks to go online.

aid*

String

Application identifier, EMV tag 4F, full TLV as read — e.g. 4F07A0000003710001.

pin_block*

String

16 hex characters. ISO-0 PIN block built from the entered PIN, the PAN, and pin_key. Omit only for a card that did not ask for a PIN.

transaction_type*

String

Purchase, Refund, Pre-Authorization or Completion.

account_type*

String

DEFAULT, SAVINGS, CURRENT or CREDIT (case-insensitive).

merchant_ref

String

Merchant defined unique reference. Max 25 characters

CARD_SEQUENCE_NUMBER

EMV tag 5F34 arrives as a full TLV: tag, length byte, then the value. Strip both the tag and the length byte, read the value as an integer, and zero-pad to three:

info

5F34 01 01 → value 0x01 → 1 → "001"

It must be exactly three characters. The processor writes this into a fixed-width field and does not validate it, so "01" or "0101" shifts every field after it. The result is not an error — the processor receives a malformed message and never replies, and your request times out with nothing to explain why.

ACCOUNT_TYPE

Squad translates to the processor's numeric code:

YOU SENDSENT OUTWARD
DEFAULT0000
SAVINGS0010
CURRENT0020
CREDIT0030

Anything else — including one of the codes — is rejected with 400. That is deliberate: the processor does not validate this field, so an unrecognised value would corrupt the message rather than fail cleanly.

chipdata is single use The cryptogram inside is bound to this amount, this transaction counter and this unpredictable number. You cannot replay a captured chipdata, and you cannot reuse one with a different amount. Each card read yields exactly one valid transaction.

Common response codes

CODEMEANINGNEXT STEP
00ApprovedComplete the sale.
51Insufficient fundsAsk for another card.
55Incorrect PINRetry with a fresh card read.
91Issuer or switch unavailableRetry later; treat the outcome as unknown until verified.
96System malfunctionRetry later; treat the outcome as unknown until verified.

Sample Request

{
"amount": 200,
"currency": "NGN",
"card_pan": "5078720085403169",
"card_expiry_month": "10",
"card_expiry_year": "28",
"card_type": "Verve",
"cardholder_name": "OPAY",
"card_sequence_number": "001",
"track2": "5078720085403169D2810601010689617F",
"chipdata": "9F2608B6960E4D3A...",
"aid": "4F07A0000003710001",
"pin_block": "02F800D37A0137E7",
"transaction_type": "Purchase",
"account_type": "SAVINGS",
"merchant_ref": "my-order-1043"
}

Responses

200:OK
Successful
{
"status": 200,
"success": true,
"message": "Success",
"data": {
"amount": 200,
"meta": {
"merchant_name": "Marvil Graphics PO Test",
"processor_amount": 200
},
"merchant_id": "ZB5BCG3Y",
"payment_info": {
"card_exp": "2905",
"card_pan": "539983******5128",
"card_type": "MasterCard",
"cardholder_name": "MARTINS/OLARINDE"
},
"status": "success",
"payment_method": "card",
"transaction_type": "Purchase",
"response_code": "00",
"transaction_reference": "ZB5BCG3Y_Demotransaction01",
"terminal_id": "2058R1KK",
"response_message": "Transaction Successful",
"rrn": "607116023876",
"stan": "023876",
"currency": "NGN",
"aid": "4F07A0000000041010",
"merchant_request_ref": null,
"updated_at": "2026-09-25T15:21:55.959Z",
"created_at": "2026-09-25T15:21:55.959Z",
"virtual_account_number": null,
"receipt_no": "0116023877",
"merchant_ref": "Demotransaction01"
}
}
200: Failed
Issuer or Switch (91)
{
"status": 200,
"success": true,
"message": "Success",
"data": {
"amount": 100,
"meta": {
"merchant_name": "Marvil Graphics PO Test",
"processor_amount": 100
},
"merchant_id": "ZB5BCG3Y",
"payment_info": {
"card_exp": "2905",
"card_pan": "539983******5128",
"card_type": "MasterCard",
"cardholder_name": "MARTINS/OLARINDE"
},
"status": "failed",
"payment_method": "card",
"transaction_type": "Purchase",
"response_code": "91",
"transaction_reference": "ZB5BCG3Y_Demo1",
"terminal_id": "2058R1KK",
"response_message": "Issuer or switch inoperative",
"rrn": "730116020264",
"stan": "020264",
"currency": "NGN",
"aid": "4F07A0000000041010",
"merchant_request_ref": null,
"updated_at": "2026-09-25T15:15:02.196Z",
"created_at": "2026-09-25T15:15:02.196Z",
"virtual_account_number": null,
"receipt_no": "0116020265",
"merchant_ref": "Demo1"
}
}

Requery Transaction using Merchant Reference​

Looks a transaction up by merchant supplied reference.

GET
https://api-d.squadco.com/softpos/transaction/by-ref/{merchant_ref}

Retrieves transaction details using merchant Ref

Parameters

Header

Authorization*

String

API keys (Secret Key) that authorize your transactions and gotten from your squad dashboard

Path Parameters

merchant_ref*

String

The unique merchant reference passed in the transaction.

Sample Response

{
"status": 200,
"success": true,
"message": "Success",
"data": {
"amount": 200,
"meta": {
"merchant_name": "Marvil Graphics PO Test",
"processor_amount": 200
},
"merchant_id": "ZB5BCG3Y",
"payment_info": {
"card_exp": "2905",
"card_pan": "539983******5128",
"card_type": "MasterCard",
"cardholder_name": "MARTINS/OLARINDE"
},
"status": "success",
"payment_method": "card",
"transaction_type": "Purchase",
"response_code": "00",
"transaction_reference": "ZB5BCG3Y_Demotransaction01",
"terminal_id": "2058R1KK",
"response_message": "Transaction Successful",
"rrn": "607116023876",
"stan": "023876",
"currency": "NGN",
"aid": "4F07A0000000041010",
"merchant_request_ref": null,
"updated_at": "2026-09-25T15:21:55.959Z",
"created_at": "2026-09-25T15:21:55.959Z",
"virtual_account_number": null,
"receipt_no": "0116023877",
"merchant_ref": "Demotransaction01"
}
}

Handling uncertain outcomes​

A card transaction can succeed at the processor while the response never reaches you — a dropped connection, a timeout, or a crash. In such situations where it is uncertain if the customer was charged you are expected to requery.

POST /transaction ──► timeout, no response ──► GET /transaction/by-ref/{merchant ref}

▼ transaction returned, it reached Squad - read. Response_code. Do not Retry
else 404 we never received it — safe to retry.

Never retry a POS transaction without checking first. This should be built as part of your flow as the merchant.