POS Integration Guide
Complete guide for integrating Point-of-Sale terminals with OLIVE for NFC card payments.
Overview
POS (Point-of-Sale) terminals allow merchants to accept OLIVE card payments. Each terminal authenticates using HMAC signatures with a per-client API key ID and secret.
NFC Cards Read contactless OLIVE cards
HMAC Auth Secure signature-based authentication
Real-time Instant payment processing
Prerequisites
Merchant Registration
Register as a processor via /processors endpoint (admin only)
Receive Credentials
Obtain API key and HMAC secret (shown once at creation)
Terminal Setup
Configure terminal with credentials
Test Transaction
Perform test payment in sandbox
API key and HMAC secret are only shown once at processor creation. Store them securely!
Authentication
POS terminals use HMAC-SHA256 signatures for authentication.
Header Description Example X-API-Key-IDPOS API key ID pos_key_abc123X-TimestampRFC3339 timestamp 2026-03-10T12:00:00ZX-SignatureHMAC-SHA256 signature a1b2c3d4e5...
Signature Generation
const crypto = require ( 'crypto' );
function signRequest ( apiKeyId , hmacSecret , method , path , rawBody ) {
const timestamp = new Date (). toISOString ();
const canonicalString = ` ${ method } \n ${ path } \n ${ timestamp } \n ${ rawBody } ` ;
const signature = crypto
. createHmac ( 'sha256' , hmacSecret )
. update ( canonicalString )
. digest ( 'hex' );
return {
'X-API-Key-ID' : apiKeyId ,
'X-Timestamp' : timestamp ,
'X-Signature' : signature ,
'Content-Type' : 'application/json'
};
}
// Example usage
const headers = signRequest (
'pos_key_abc123' ,
'secret_xyz789' ,
'POST' ,
'/api/v1/pos/payment' ,
JSON . stringify ({
card_serial: 'OLIV0001' ,
merchant_id: 'MERCHANT001' ,
terminal_id: 'TERM001' ,
amount: '150.00' ,
currency: 'SLE' ,
transaction_ref: 'POS-TXN-123' ,
pin: '1234' ,
processor_id: 'proc-uuid-123'
})
);
Signatures are valid for 5 minutes. Ensure your terminal clock is synchronized with NTP.
Payment Flow
Card Tap
Customer taps NFC card on terminal
Read Card
Terminal reads card serial number via NFC
Verify Card
Call POST /api/v1/pos/verify-card to validate card
Collect PIN
If valid, prompt customer for 4-digit PIN
Process Payment
Call POST /api/v1/pos/payment with amount and PIN
Display Result
Show success/failure message to customer
Sequence Diagram
API Endpoints
Verify Card
Check if card is valid and get cardholder info:
curl -X POST "https://demo.api.vultlocal.com/api/v1/pos/verify-card" \
-H "X-API-Key-ID: pos_key_abc123" \
-H "X-Timestamp: 2026-03-10T12:00:00Z" \
-H "X-Signature: a1b2c3d4..." \
-H "Content-Type: application/json" \
-d '{
"card_serial": "OLIV0001",
"pin": "1234"
}'
Response:
{
"success" : true ,
"message" : "Card verified successfully" ,
"is_active" : true ,
"balance" : "1500.00" ,
"holder_name" : "John Doe"
}
Process Payment
Execute payment after PIN verification:
curl -X POST "https://demo.api.vultlocal.com/api/v1/pos/payment" \
-H "X-API-Key-ID: pos_key_abc123" \
-H "X-Timestamp: 2026-03-10T12:00:00Z" \
-H "X-Signature: a1b2c3d4..." \
-H "Content-Type: application/json" \
-d '{
"card_serial": "OLIV0001",
"merchant_id": "MERCHANT001",
"terminal_id": "TERM001",
"amount": "150.00",
"currency": "SLE",
"pin": "1234",
"transaction_ref": "INV-12345",
"processor_id": "proc-uuid-123"
}'
Response:
fee_amount is dynamic and comes from the active POS fee configuration. If the merchant setup charges a fee for the transaction, this field returns that amount instead of 0.00.
{
"success" : true ,
"message" : "Payment successful" ,
"transaction_id" : "txn_abc123" ,
"approval_code" : "882211" ,
"amount" : "150.00" ,
"remaining_balance" : "1450.00" ,
"fee_amount" : "1.50"
}
Refund Payment
Refund a previous transaction:
curl -X POST "https://demo.api.vultlocal.com/api/v1/pos/refund" \
-H "X-API-Key-ID: pos_key_abc123" \
-H "X-Timestamp: 2026-03-10T12:00:00Z" \
-H "X-Signature: a1b2c3d4..." \
-H "Content-Type: application/json" \
-d '{
"original_transaction_id": "txn_abc123",
"amount": "150.00",
"reason": "Customer request",
"initiated_by": "admin_user_01"
}'
Error Handling
Error Code Meaning Terminal Action HMAC auth error Missing/invalid HMAC headers Check API key ID, timestamp, and signature Validation error Missing or malformed request fields Fix the request payload Invalid PIN Wrong PIN entered Retry with the correct PIN Insufficient funds Not enough balance Reduce amount or use another card
Error Response Example
{
"success" : false ,
"success" : false ,
"error" : "Insufficient funds"
}
Terminal Implementation
Sample Terminal Code
const axios = require ( 'axios' );
const crypto = require ( 'crypto' );
class OlivePOS {
constructor ( apiKeyId , hmacSecret , baseUrl ) {
this . apiKeyId = apiKeyId ;
this . hmacSecret = hmacSecret ;
this . baseUrl = baseUrl ;
}
signRequest ( method , path , body ) {
const timestamp = new Date (). toISOString ();
const rawBody = JSON . stringify ( body );
const canonical = ` ${ method } \n ${ path } \n ${ timestamp } \n ${ rawBody } ` ;
const signature = crypto
. createHmac ( 'sha256' , this . hmacSecret )
. update ( canonical )
. digest ( 'hex' );
return {
'X-API-Key-ID' : this . apiKeyId ,
'X-Timestamp' : timestamp ,
'X-Signature' : signature ,
'Content-Type' : 'application/json'
};
}
async verifyCard ( cardSerial , pin ) {
const path = '/api/v1/pos/verify-card' ;
const body = { card_serial: cardSerial , pin };
const headers = this . signRequest ( 'POST' , path , body );
const response = await axios . post (
` ${ this . baseUrl }${ path } ` ,
body ,
{ headers }
);
return response . data ;
}
async processPayment ( cardSerial , amount , pin , transactionRef ) {
const path = '/api/v1/pos/payment' ;
const body = {
card_serial: cardSerial ,
merchant_id: 'MERCHANT001' ,
terminal_id: 'TERM001' ,
amount ,
currency: 'SLE' ,
pin ,
transaction_ref: transactionRef ,
processor_id: 'proc-uuid-123'
};
const headers = this . signRequest ( 'POST' , path , body );
const response = await axios . post (
` ${ this . baseUrl }${ path } ` ,
body ,
{ headers }
);
return response . data ;
}
}
// Usage
const pos = new OlivePOS (
'pos_key_abc123' ,
'secret_xyz789' ,
'https://demo.api.vultlocal.com'
);
// Verify card
const cardInfo = await pos . verifyCard ( 'OLIV0001' , '1234' );
console . log ( `Cardholder: ${ cardInfo . holder_name } ` );
// Process payment
const result = await pos . processPayment ( 'OLIV0001' , '150.00' , '1234' , 'INV-001' );
console . log ( `Transaction: ${ result . transaction_id } ` );
Best Practices
Security
Store HMAC secret securely (encrypted)
Use HTTPS only
Never log credentials
Implement PIN attempt limits
User Experience
Show cardholder name for verification
Display balance before payment
Print/show receipts
Clear error messages
Reliability
Handle network errors gracefully
Implement retry with same reference
Sync terminal clock with NTP
Queue failed transactions
Compliance
Log all transactions locally
Daily reconciliation
Keep receipts/records
Report issues promptly
Testing
Sandbox Environment
Use sandbox credentials for testing:
Environment Base URL Sandbox https://sandbox-api.olive.example.comDemo https://demo.api.vultlocal.com
Test Cards
Card Serial PIN Behavior TEST00011234Success (balance: 100,000) TEST00021234Insufficient balance TEST0003any Card blocked
POS Payment API Payment endpoint reference
Verify Card API Card verification endpoint
Processor Setup Register as processor
Security Guide HMAC authentication details