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.
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.
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.
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
{
"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"
}
}
}
{
"status": 400,
"success": false,
"message": "Incorrect device information",
"data": {}
}
{
"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.
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.
'
Most Nigerian cards require an online PIN. Two things follow:
- The card will not produce a cryptogram at all unless a PIN was entered — so without one you get no usable chipdata.
- 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
{
"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"
}
}
{
"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"
}
}
{
"status": 400,
"success": false,
"message": "Incorrect device information",
"data": {}
}
{
"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.
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:
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 SEND | SENT OUTWARD |
|---|---|
| DEFAULT | 0000 |
| SAVINGS | 0010 |
| CURRENT | 0020 |
| CREDIT | 0030 |
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
| CODE | MEANING | NEXT STEP |
|---|---|---|
| 00 | Approved | Complete the sale. |
| 51 | Insufficient funds | Ask for another card. |
| 55 | Incorrect PIN | Retry with a fresh card read. |
| 91 | Issuer or switch unavailable | Retry later; treat the outcome as unknown until verified. |
| 96 | System malfunction | Retry 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
{
"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"
}
}
{
"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.
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.