Developer docs
Integrate ShopSwift's QR payment orchestration layer into any POS system, ERP, or custom backend.
API Overview
The ShopSwift API is a REST API with JSON request/response bodies. All endpoints are prefixed with /api. Amounts are always in kobo (₦1 = 100 kobo).
Base URL
https://shopswiftpay.com
Transaction status lifecycle
pending — transaction record created session_created — QR token generated, awaiting customer scan payment_initiated — customer selected a payment method transfer_reported — customer tapped "I've Paid", awaiting cashier verify confirmed — cashier verified and confirmed payment expired — QR session TTL elapsed without payment cancelled — POS cancelled the session failed — webhook validation failed
Authentication
ShopSwift uses API key authentication. Two key types exist — keep both server-side only, never expose them in client code.
Used by POS systems to create and manage payment sessions. Scoped to a single store. Found in Dashboard → Settings → API Keys.
POST /api/pos/transactions X-Store-Key: sk_live_xxxxxxxxxxxx
Used to fetch analytics, transaction lists, store config, and to manually confirm payments. Separate from the store key for security.
GET /api/dashboard/stats X-Dashboard-Key: dk_live_xxxxxxxxxxxx
POS API
Create a payment session
Called by your POS when a customer is ready to pay. Returns a payUrl to display as a QR code or share with the customer.
POST /api/pos/transactions
X-Store-Key: your_store_api_key
Content-Type: application/json
{
"posRef": "TXN-849201",
"amount": 1730000,
"items": [
{ "name": "Premium Rice 5kg", "qty": 1, "unitPrice": 850000, "total": 850000 },
{ "name": "Vegetable Oil 1L", "qty": 2, "unitPrice": 420000, "total": 840000 }
],
"posCallbackUrl": "https://your-pos.com/shopswift/callback"
}Response:
HTTP 201 Created
{
"sessionId": "a3f2c891-7d4b-4e1a-b6c5-02f8e3d1a9b7",
"qrToken": "e1b9d3f7-22a4-4c8b-9f1e-8a7b2c6d4e0f",
"payUrl": "https://shopswiftpay.com/pay/e1b9d3f7-22a4-4c8b-9f1e-8a7b2c6d4e0f",
"amount": 1730000,
"status": "session_created"
}All amounts are in kobo. posCallbackUrl is optional — you can set a default in Dashboard → Settings instead.
Poll session status
Poll every 2–3 seconds until status is confirmed, expired, or cancelled. Prefer the outbound webhook callback over polling where possible.
GET /api/pos/transactions/{sessionId}/status
X-Store-Key: your_store_api_key
HTTP 200 OK
{
"sessionId": "a3f2c891-7d4b-4e1a-b6c5-02f8e3d1a9b7",
"status": "confirmed",
"confirmedAt": "2024-01-15T10:18:42Z"
}Cancel a session
Cancel an active session before the customer pays — for example when the customer abandons the checkout.
DELETE /api/pos/transactions/{sessionId}
X-Store-Key: your_store_api_key
HTTP 200 OK
{ "sessionId": "a3f2c891-7d4b-4e1a-b6c5-02f8e3d1a9b7", "status": "cancelled" }Webhooks
Outbound — POS confirmation callback
When a transaction is confirmed, ShopSwift sends a POST to your posCallbackUrl. ShopSwift retries up to 3 times with exponential backoff if your server doesn't respond with 2xx.
POST https://your-pos.com/shopswift/callback
Content-Type: application/json
X-ShopSwift-Signature: sha256=abc123...
{
"event": "shopswift.payment_confirmed",
"sessionId": "a3f2c891-7d4b-4e1a-b6c5-02f8e3d1a9b7",
"posRef": "TXN-849201",
"storeId": "6baa67d4-950c-4b6a-9f3b-bb21d2c4d3c6",
"amount": 1730000,
"confirmedAt": "2024-01-15T10:18:42Z"
}Signature verification
Every outbound callback includes an X-ShopSwift-Signature header. Always verify it before processing to prevent spoofed callbacks.
import crypto from "crypto";
function verifyShopSwiftSignature(
rawBody: string,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return `sha256=${expected}` === signature;
}
// In your webhook handler:
app.post("/shopswift/callback", (req, res) => {
const sig = req.headers["x-shopswift-signature"] as string;
const valid = verifyShopSwiftSignature(
JSON.stringify(req.body), sig, SHOPSWIFT_WEBHOOK_SECRET
);
if (!valid) return res.status(401).send("Invalid signature");
// Process the confirmed payment...
res.json({ received: true });
});Inbound — Payment gateway webhooks
ShopSwift also accepts inbound webhooks from Paystack and Flutterwave for automated payment confirmation without cashier involvement.
// ShopSwift also accepts inbound webhooks from payment gateways. // Configure your Paystack webhook URL to: POST /api/webhooks/paystack // And Flutterwave to: POST /api/webhooks/flutterwave // // ShopSwift verifies the HMAC signature on every inbound webhook // and transitions the transaction to "confirmed" automatically.
Configure your Paystack/Flutterwave dashboard to send webhook events to these URLs. ShopSwift verifies the HMAC signature on every inbound event.
Dashboard API
Endpoints for fetching store analytics, transaction history, and managing transactions. All require X-Dashboard-Key.
GET /api/dashboard/stats
Revenue and volume summary for a date range. Defaults to the last 30 days.
GET /api/dashboard/stats?from=2024-01-01&to=2024-01-31
X-Dashboard-Key: your_dashboard_key
HTTP 200 OK
{
"totalRevenue": 48250000,
"confirmedTransactions": 312,
"pendingTransactions": 4,
"cancelledTransactions": 12,
"expiredTransactions": 8,
"averageConfirmationTime": 42
}GET /api/dashboard/transactions
Paginated transaction list. Filter by status, from, and to date.
GET /api/dashboard/transactions?page=1&limit=20&status=confirmed
X-Dashboard-Key: your_dashboard_key
HTTP 200 OK
{
"transactions": [ { "sessionId": "...", "amount": 173000, "status": "confirmed", ... } ],
"total": 312,
"page": 1,
"pages": 16
}POST /api/dashboard/transactions/:id/confirm
Manually confirm a transaction — used by cashiers after they verify the bank transfer. Transitions status from transfer_reported to confirmed and triggers the POS callback.
POST /api/dashboard/transactions/{sessionId}/confirm
X-Dashboard-Key: your_dashboard_key
HTTP 200 OK
{
"sessionId": "a3f2c891-7d4b-4e1a-b6c5-02f8e3d1a9b7",
"status": "confirmed",
"confirmedAt": "2024-01-15T10:18:42Z"
}X-Dashboard-Key — not X-Store-Key. Keep these keys separate and server-side only.