Skip to content

Simpaisa API Architectural Standards

Owner Classification Review Date Status
Architecture Practice Internal April 2027 Active

Simpaisa API Architectural Standards

Version: 1.0.0
Effective Date: 2026-04-01
Owner: CDO / Architecture
Status: Active
Applicability: All Simpaisa API products — Pay-Ins, Pay-Outs (Disbursements), Remittances, Cards, and any future API families

Compliance: All new API endpoints MUST comply with these standards. Existing endpoints MUST comply by the next major version. Exceptions require written CDO approval with a documented migration timeline.


Table of Contents

  1. Design Philosophy

  2. OpenAPI Specification

  3. URL Design

  4. HTTP Methods

  5. Versioning

  6. Authentication & Security

  7. Request Design

  8. Response Design

  9. Error Handling

  10. HTTP Status Codes

  11. Idempotency

  12. Pagination

  13. Rate Limiting

  14. Webhooks & Async Notifications

  15. Transaction Lifecycle & State Machines

  16. Field Validation & Data Types

  17. FX Rates & Currency Handling

  18. OTP & Multi-Factor Authentication Flows

  19. Bulk & Batch Operations

  20. Reconciliation & Settlement

  21. Sandbox & Testing

  22. Transport Security

  23. PII Handling & Data Retention

  24. Health & Status Endpoints

  25. Documentation Separation

  26. Naming Conventions

  27. Content Negotiation

  28. Changelog & Deprecation

  29. API Design Checklist

  30. Testing & Compliance

  31. Appendix: Audit Findings Cross-Reference


1. Design Philosophy

Simpaisa APIs handle real money. The cost of ambiguity, inconsistency, or missing documentation is measured in duplicate payments, failed integrations, and lost merchant trust. These standards exist to prevent that.

Principles:

  1. Contract over implementation. API docs describe what the merchant sees (inputs, outputs, behavior, SLAs), never how the system works internally (database tables, schedulers, cache layers).

  2. Consistency across products. A merchant integrating Pay-Ins and Pay-Outs should feel like they are using one platform, not two different companies' APIs.

  3. Explicit over implicit. Every behavior must be documented. If a rate quote expires after 30 seconds, say so. If webhooks retry 3 times, say so. If OTPs expire in 5 minutes, say so.

  4. Safe by default. Idempotency, signature verification, TLS, rate limiting — these are not optional features, they are baseline requirements for a payment API.

  5. Machine-readable specifications. OpenAPI 3.1 is the single source of truth. Documentation, SDKs, validation rules, and contract tests all generate from the spec.


2. OpenAPI Specification

Addresses: SA Audit Finding 10, GitBook Findings 6, 7, 13

2.1 OpenAPI as Source of Truth

All Simpaisa APIs MUST be defined in OpenAPI 3.1 specification files. The OpenAPI spec is the canonical source of truth — not markdown docs, not GitBook pages, not Confluence.

One spec file per API product:

File Product
payin-api.yaml Pay-Ins (Wallets, Cards, IBFT, Hosted, Refunds)
payout-api.yaml Pay-Outs (Disbursements)
remittance-api.yaml Remittances
cards-api.yaml Cards (Payments, Inquiry, Capture, Void, Finalize, Refunds)

2.2 What Generates from the Spec

Artifact Tool
Developer portal Swagger UI or Redocly
Client SDKs openapi-generator (Java, Python, Node.js, PHP, C#)
Spec linting Spectral (Stoplight) with custom Simpaisa ruleset
Contract tests Schemathesis, Dredd, or Prism
Mock server Prism (for merchant sandbox testing)

2.3 Spec Requirements

Every endpoint in the OpenAPI spec MUST include:

  • Summary and description

  • All request headers (auth, content-type, idempotency key)

  • Request body schema with required fields marked, types, constraints (minLength, maxLength, pattern, enum, minimum, maximum)

  • Response schemas for every returned HTTP status code (success and each error case)

  • At least one example for request and response (success and one failure)

  • security scheme reference

  • tags for product grouping

2.4 Linting Rules (Spectral)

A custom Spectral ruleset MUST enforce these standards in CI. Any PR that modifies an OpenAPI spec file MUST pass linting before merge. Minimum rules:

  • Every POST that creates a resource has an Idempotency-Key header parameter

  • Every response includes traceId

  • Every 4xx/5xx response uses the standard error schema

  • Every list endpoint has page and limit query parameters

  • Every response includes rate limit headers

  • No $ref to internal-only schemas in merchant-facing specs

  • All paths start with /v{N}/


3. URL Design

Addresses: SA Audit Findings 7, 11; GitBook Findings 8, 9

3.1 Standard URL Pattern

All API endpoints MUST follow this pattern:

https://api.simpaisa.com/v{version}/{product}/{merchantId}/{resource}[/{action}]

Examples:

POST   /v1/disbursements/{merchantId}/transactions/initiate
GET    /v1/disbursements/{merchantId}/transactions/{transactionId}
GET    /v1/disbursements/{merchantId}/transactions/{transactionId}/status
GET    /v1/disbursements/{merchantId}/banks
GET    /v1/disbursements/{merchantId}/balance

POST   /v1/remittances/{merchantId}/transactions/initiate
GET    /v1/remittances/{merchantId}/transactions/{transactionId}
GET    /v1/remittances/{merchantId}/fx-rates

POST   /v1/payin/{merchantId}/transactions/initiate
POST   /v1/payin/{merchantId}/transactions/{transactionId}/verify

POST   /v1/cards/{merchantId}/payments/initiate
POST   /v1/cards/{merchantId}/payments/{paymentId}/capture

3.2 URL Rules

Rule Standard Example
Base URL Single domain: api.simpaisa.com Not 7 different subdomains
Path casing kebab-case /fx-rates not /getFxRate or /FxRates
Resource names Plural nouns /transactions not /transaction
Actions Verb suffix only when necessary /initiate, /verify, /capture
No /list suffix GET on a collection implies listing /banks not /banks/list
merchantId In the URL path, validated against auth Not in the request body
No query params for sensitive data Account numbers, IBANs in body, not URL Security: URLs appear in logs

3.3 Base URL Consolidation

Target state: All products served from a single domain with path-based routing.

Environment Base URL
Production https://api.simpaisa.com
Sandbox https://sandbox.simpaisa.com

All products (Pay-Ins, Pay-Outs, Remittances, Cards) differentiated by path, not subdomain. The commerceplex.com domain for Remittances MUST be migrated to simpaisa.com.

3.4 merchantId Validation

The merchantId in the URL path MUST be validated against the authenticated merchant's identity. If merchant A authenticates and uses merchant B's ID in the URL, the API MUST return 403 Forbidden. This prevents IDOR (Insecure Direct Object Reference) attacks.


4. HTTP Methods

Addresses: SA Audit Finding 8

4.1 Method Semantics

Method Usage Idempotent Safe Cacheable
GET Read a resource or list resources Yes Yes Yes
POST Create a resource or trigger an action No (unless idempotency key) No No
PUT Full replace of a resource Yes No No
PATCH Partial update of a resource No No No
DELETE Remove a resource Yes No No

4.2 Rules

  • GET for reads. All query/fetch/list/status operations MUST use GET. Exceptions: if the query payload contains sensitive data (account numbers, IBANs) that cannot be safely placed in query parameters, use POST /resource/search explicitly named as a search operation.

  • POST for state changes only. Transaction initiation, registration, confirmation, refunds — all state-changing operations use POST.

  • No POST for reads. Endpoints like fetch-account, getFxRate, banks/list MUST be GET.

  • No verb-based URLs for CRUD. Use DELETE /transactions/{id} not POST /transactions/{id}/delete.

4.3 Migration from POST-for-Reads

Changing an HTTP method is a breaking change. When migrating existing POST-to-GET endpoints:

  1. Introduce the GET endpoint under a new API version

  2. Keep the POST endpoint on the old version with a Sunset header

  3. Run both versions in parallel for minimum 6 months

  4. Do NOT use HTTP 301/302 redirects — they can change POST to GET in non-compliant HTTP clients, which will break payment calls


5. Versioning

Addresses: SA Audit Finding 13; GitBook Finding 23

5.1 Versioning Strategy

URL path versioning: /v1/, /v2/, etc.

All API URLs MUST include a version prefix. The version number is a single integer — no minor or patch versions in the URL.

/v1/disbursements/{merchantId}/transactions/initiate
/v2/disbursements/{merchantId}/transactions/initiate

Do NOT use header-based versioning (version: 3.0) as the primary mechanism. If a version header currently exists for backward compatibility, document it but deprecate it in favor of path versioning.

5.2 What Constitutes a Breaking Change

Breaking (requires new version):

  • Removing or renaming a field from a response

  • Changing a field's type (string → integer)

  • Changing the URL path or HTTP method

  • Changing the authentication mechanism

  • Changing error code values or meanings

  • Removing an endpoint

  • Making a previously optional request field required

Non-breaking (same version):

  • Adding a new optional field to request or response

  • Adding a new endpoint

  • Adding a new error code

  • Adding new enum values to existing fields

  • Increasing rate limits

  • Adding new webhook event types

5.3 Deprecation Policy

Phase Action Timeline
Announcement Email merchants + changelog entry Day 0
Sunset header Add Sunset: <date> header to old version responses Day 0
Migration guide Publish guide with side-by-side old/new examples Day 0
Warning logs Log usage of deprecated version for internal tracking Day 0
Migration support Active outreach to high-volume merchants Month 1-4
End of life Reject requests to old version with 410 Gone Month 6+

Minimum deprecation window: 6 months for all changes. 12 months for fundamental changes (auth mechanism, URL structure).


6. Authentication & Security

Addresses: SA Audit Finding 5; GitBook Finding 1

6.1 Unified Authentication Standard

All Simpaisa API products MUST use the same authentication mechanism: RSA-SHA256 request signing with mutual TLS for server-to-server communication.

No product may use session-based authentication (JSESSIONID) or plain API key authentication without signing. These are not suitable for payment APIs.

6.2 Authentication Headers

Every authenticated API request MUST include:

Header Format Description
X-Merchant-Id String Merchant identifier
X-Timestamp ISO 8601 UTC Request timestamp (e.g., 2026-04-01T12:00:00Z)
X-Signature Base64-encoded RSA-SHA256 Signature over the canonical request string
Content-Type application/json Request body format

6.3 Signature Generation

The signature is computed as:

canonical_string = HTTP_METHOD + "
"
                 + REQUEST_PATH + "
"
                 + X-Timestamp + "
"
                 + SHA256(request_body)

signature = RSA_SHA256_SIGN(canonical_string, merchant_private_key)
encoded   = BASE64_ENCODE(signature)

Requirements:

  • RSA key size: minimum 2048-bit, PKCS#8 format

  • Hash algorithm: SHA-256

  • Signature encoding: Base64

6.4 Timestamp Validation

  • Clock skew tolerance: 5 minutes

  • Requests with timestamps older than 5 minutes MUST be rejected with 401 Unauthorized and error code TIMESTAMP_EXPIRED

  • This prevents replay attacks

6.5 Key Management

The API documentation MUST cover:

  1. Credential provisioning: How merchants obtain their merchantId, RSA key pairs, and any API secrets

  2. Key rotation: How to rotate keys without downtime (support two active keys during rotation, deactivate old key after confirmation)

  3. Key revocation: How to immediately revoke a compromised key

  4. IP whitelisting: Optional allowlist of merchant IP addresses for additional security

6.6 Code Samples

Authentication documentation MUST include working signature generation code samples in at minimum:

  • Java

  • Python

  • Node.js

  • PHP

  • C#

Each sample must be copy-paste runnable with test credentials.


7. Request Design

Addresses: SA Audit Finding 10; GitBook Findings 15, 21

7.1 Request Body Format

All request bodies MUST be flat JSON. No request wrapper object.

Correct:

{
  "reference": "PAY-20260401-001",
  "amount": "50000.00",
  "currency": "PKR",
  "beneficiaryAccount": "03001234567"
}

Incorrect:

{
  "request": {
    "reference": "PAY-20260401-001",
    "amount": "50000.00"
  }
}

Products currently using request wrappers (Pay-Outs, Remittances) MUST migrate to flat JSON in the next major version.

7.2 Field Naming

  • Use camelCase for all JSON field names: merchantId, transactionId, beneficiaryName

  • Be consistent: the same concept uses the same field name across all products (see Section 26: Naming Conventions)

  • Never use abbreviations that are not universally understood: txnIdtransactionId, amtamount

7.3 Required vs Optional

Every field in the OpenAPI spec MUST be marked required or optional. Optional fields MUST document their default value or behavior when omitted.


8. Response Design

Addresses: SA Audit Findings 2, 10; GitBook Findings 3, 6, 7

8.1 Standard Success Response

All successful responses MUST follow this structure:

{
  "data": {
    "transactionId": "TXN-2026-04-01-0001",
    "status": "INITIATED",
    "reference": "PAY-20260401-001",
    "amount": "50000.00",
    "currency": "PKR",
    "createdAt": "2026-04-01T12:00:00Z"
  },
  "traceId": "abc-123-def-456"
}

Rules:

  • The response payload is always under a data key for single resources

  • For list responses, data is an array with a sibling pagination object (see Section 12)

  • Every response includes a traceId that maps to the internal distributed trace (OpenSearch/Jaeger) for debugging

  • No success: true boolean field. HTTP status codes are the source of truth

  • No response wrapper object

8.2 Response Schema Completeness

Every endpoint MUST document the full response schema for:

  1. Success response (all fields, types, whether each is always present or conditional)

  2. Error response for each relevant HTTP status code

  3. Which request fields are echoed back in the response


9. Error Handling

Addresses: SA Audit Findings 2, 6; GitBook Findings 3, 4

9.1 Standard Error Response Schema

All error responses (HTTP 4xx and 5xx) MUST use this schema:

{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Merchant balance is insufficient for this disbursement",
    "details": [
      {
        "field": "amount",
        "issue": "Requested 50000.00 PKR but available balance is 12340.00 PKR"
      }
    ]
  },
  "traceId": "abc-123-def-456",
  "timestamp": "2026-04-01T12:00:00Z"
}

9.2 Error Schema Rules

Field Required Description
error.code Yes Machine-readable error code, UPPER_SNAKE_CASE
error.message Yes Human-readable description, safe to display to merchant developers
error.details No Array of field-level errors for validation failures
error.details[].field Yes (if details present) The request field that caused the error
error.details[].issue Yes (if details present) Description of what's wrong with the field
traceId Yes Distributed trace identifier
timestamp Yes ISO 8601 UTC timestamp of the error

9.3 Error Code Conventions

Error codes MUST be:

  • Machine-readable: INSUFFICIENT_BALANCE, not "Your balance is too low"

  • UPPER_SNAKE_CASE: INVALID_ACCOUNT, RATE_LIMIT_EXCEEDED

  • Product-agnostic where possible: Use INVALID_ACCOUNT across all products, not PAYIN_INVALID_ACCOUNT

  • Documented per endpoint: Every endpoint lists all possible error codes

9.4 Standard Error Codes

These error codes MUST be used consistently across all products:

Error Code HTTP Status Meaning
INVALID_REQUEST 400 Malformed request body or missing required field
AUTHENTICATION_FAILED 401 Missing, invalid, or expired credentials
TIMESTAMP_EXPIRED 401 Request timestamp outside clock skew tolerance
INVALID_SIGNATURE 401 RSA signature verification failed
FORBIDDEN 403 Authenticated but not authorized for this resource
MERCHANT_MISMATCH 403 merchantId in URL does not match authenticated merchant
RESOURCE_NOT_FOUND 404 Transaction, account, or resource does not exist
IDEMPOTENCY_CONFLICT 409 Same idempotency key with different request body
DUPLICATE_REFERENCE 409 Reference already exists for this merchant
VALIDATION_ERROR 422 Business rule validation failure
INSUFFICIENT_BALANCE 422 Merchant balance too low for this transaction
INVALID_ACCOUNT 422 Beneficiary account invalid or not found
CHANNEL_UNAVAILABLE 422 Payment channel (e.g., Easypaisa, 1Link) is down
QUOTE_EXPIRED 422 FX rate quote has expired
AMOUNT_OUT_OF_RANGE 422 Amount below minimum or above maximum
OTP_EXPIRED 422 OTP has expired
OTP_INVALID 422 Incorrect OTP provided
OTP_MAX_ATTEMPTS 429 Maximum OTP verification attempts exceeded
RATE_LIMIT_EXCEEDED 429 Too many requests
INTERNAL_ERROR 500 Unexpected server error
UPSTREAM_ERROR 502 Payment channel returned an unexpected error
SERVICE_UNAVAILABLE 503 System is temporarily unavailable

9.5 Simpaisa-Specific Status Code Mapping

Products currently using numeric status codes (0000, 0001, 0006, 0054, etc.) MUST map these to the standard error codes above. A mapping table MUST be published so merchants can migrate their error handling.


10. HTTP Status Codes

Addresses: SA Audit Finding 6; GitBook Finding 4

10.1 Required HTTP Status Codes

Every endpoint MUST document which HTTP status codes it returns. The API MUST use proper HTTP status codes — not HTTP 200 for everything with an internal error code.

Status When to Use
200 OK Successful GET, successful POST that returns existing resource (idempotent replay)
201 Created Successful POST that creates a new resource
204 No Content Successful DELETE
400 Bad Request Malformed JSON, missing Content-Type, syntactically invalid request
401 Unauthorized Missing, invalid, or expired authentication credentials
403 Forbidden Authenticated but not authorized (e.g., merchantId mismatch)
404 Not Found Resource does not exist
409 Conflict Idempotency key reuse with different params, duplicate reference
422 Unprocessable Entity Business validation failures (insufficient balance, invalid account, expired quote)
429 Too Many Requests Rate limit exceeded. MUST include Retry-After header
500 Internal Server Error Unexpected server error
502 Bad Gateway Upstream payment channel error
503 Service Unavailable System temporarily unavailable. MUST include Retry-After header

10.2 HTTP 200 for Errors (Legacy)

If a product currently returns HTTP 200 for all responses (including errors) with the error indicated by an internal status code:

  1. Document this behavior explicitly so merchants don't waste time debugging

  2. Migrate to proper HTTP status codes in the next major version

  3. During the migration period, include both the HTTP status code and the legacy internal status code in the response body


11. Idempotency

Addresses: SA Audit Finding 1; GitBook Finding 2

11.1 Scope

Every state-changing endpoint (POST, PUT, PATCH, DELETE) that initiates, modifies, or reverses a financial transaction MUST support idempotency.

Mandatory endpoints include:

  • Transaction initiation (all products)

  • Transaction verification/confirmation

  • Refund initiation

  • Customer registration

  • FX quote confirmation

11.2 Idempotency Key Header

Idempotency-Key: <UUID v4>

The merchant provides a unique key in the Idempotency-Key request header. UUID v4 format is recommended but any unique string up to 128 characters is accepted.

11.3 Server Behavior

Scenario Response
First request with key K Process normally, store (merchantId, K, response). Return 201 Created
Repeat request with key K, same body Return stored response with 200 OK. Do NOT reprocess
Repeat request with key K, different body Return 409 Conflict with error code IDEMPOTENCY_CONFLICT
Key K after expiry window Process as new request

11.4 Idempotency Window

Keys MUST be stored for a minimum of 48 hours. After 48 hours, the same key may be reused for a new request. Document this window in the API spec.

11.5 Storage

Idempotency keys MUST be stored per-merchant to prevent cross-merchant collisions. Implementation options (engineering decision):

  • Per-service MySQL with index on (merchant_id, idempotency_key) and TTL cleanup

  • Redis with TTL for lower latency at high volume

  • API Gateway layer (shared, DRY)

11.6 Existing reference Field

If a product uses a reference field as a unique identifier, document clearly whether it provides idempotent behavior:

  • If yes: document that same reference + same params returns original response (same as idempotency key behavior)

  • If no (returns error like REFERENCE_ALREADY_EXISTS): the merchant cannot distinguish "my first request succeeded" from "my first request failed and the reference was still consumed." This MUST be fixed.


12. Pagination

Addresses: SA Audit Finding 9; GitBook Finding 11

12.1 All List Endpoints MUST Paginate

Any endpoint that returns a collection of resources MUST support pagination. Never return an unbounded result set.

12.2 Pagination Parameters

Parameter Type Default Max Description
page integer 1 Page number (1-indexed)
limit integer 20 100 Items per page

12.3 Pagination Response

{
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "totalCount": 12345,
    "totalPages": 618,
    "hasNext": true
  },
  "traceId": "abc-123-def-456"
}

12.4 Cursor-Based Pagination

For high-volume endpoints (transaction history, event logs), use cursor-based pagination instead of offset-based. Offset-based pagination degrades at scale (OFFSET 10000 scans 10,000 rows in MySQL).

GET /v1/disbursements/{merchantId}/transactions?cursor=eyJpZCI6MTIzNH0&limit=50

Response includes:

{
  "data": [ ... ],
  "pagination": {
    "limit": 50,
    "hasNext": true,
    "nextCursor": "eyJpZCI6MTI4NH0"
  }
}

Document which endpoints use cursor-based vs offset-based pagination.


13. Rate Limiting

Addresses: SA Audit Finding 12; GitBook Finding 12

13.1 Rate Limit Documentation

Every endpoint MUST document its rate limit. Rate limits may vary by endpoint type:

Endpoint Type Example Limit
Transaction initiation 100 requests/minute
Status inquiry 500 requests/minute
Reference data (banks, FX rates) 1000 requests/minute
Bulk operations 10 requests/minute

13.2 Rate Limit Response Headers

Every API response (not just 429 responses) MUST include these headers:

Header Description Example
X-RateLimit-Limit Max requests allowed in window 100
X-RateLimit-Remaining Requests remaining in current window 87
X-RateLimit-Reset Unix timestamp when the window resets 1711972800

13.3 Rate Limit Exceeded Response

When a merchant exceeds the rate limit:

  • HTTP status: 429 Too Many Requests

  • Retry-After header with seconds until the window resets

  • Standard error body with code RATE_LIMIT_EXCEEDED

13.4 Tiered Limits

If different merchant tiers have different rate limits, document the tiers and how merchants can request higher limits.


14. Webhooks & Async Notifications

Addresses: SA Audit Finding 4; GitBook Findings 5, 20

14.1 Webhook Requirements

Every product that uses asynchronous processing MUST provide a complete webhook specification covering all of the following:

14.2 Event Types

Use namespaced event types:

disbursement.initiated
disbursement.completed
disbursement.failed
disbursement.reversed
remittance.initiated
remittance.completed
remittance.failed
remittance.on_hold
remittance.aml_review
payin.initiated
payin.completed
payin.failed
payin.refunded

Document which transaction states trigger webhooks and which do not. If states like stuck, aml_review, or in_review do not trigger webhooks, document that explicitly and recommend a polling interval for merchants to detect these states.

14.3 Webhook Payload

{
  "eventId": "evt_abc123def456",
  "eventType": "disbursement.completed",
  "timestamp": "2026-04-01T12:05:00Z",
  "data": {
    "transactionId": "TXN-2026-04-01-0001",
    "reference": "PAY-20260401-001",
    "status": "COMPLETED",
    "amount": "50000.00",
    "currency": "PKR",
    "completedAt": "2026-04-01T12:05:00Z"
  }
}

14.4 Signature Verification

Every webhook request from Simpaisa MUST include a signature header so merchants can verify authenticity:

X-Simpaisa-Signature: t=1711972800,v1=abc123...

Signature format:

signed_payload = timestamp + "." + raw_request_body
signature = HMAC_SHA256(signed_payload, webhook_secret)

Documentation MUST include:

  • Step-by-step verification instructions

  • Code samples in 3+ languages

  • Guidance on timestamp validation (reject webhooks older than 5 minutes to prevent replay attacks)

14.5 Delivery Guarantees

Property Standard
Delivery guarantee At-least-once
Deduplication Merchants MUST handle duplicate deliveries (use eventId for deduplication)
Ordering Webhooks MAY arrive out of order. Merchants should use timestamp and transaction status, not arrival order

14.6 Retry Policy

Attempt Delay Notes
1st retry 1 minute After initial delivery failure
2nd retry 5 minutes
3rd retry 30 minutes
4th retry 2 hours
5th retry 24 hours Final attempt

After all retries are exhausted, mark the webhook as failed. Provide a webhook event log in the merchant dashboard where merchants can see delivery status and manually retry.

14.7 Merchant Response Requirements

  • Merchant endpoint MUST return HTTP 200 within 5 seconds

  • Any non-2xx response or timeout triggers retry

  • Merchant endpoint MUST be HTTPS

  • If the merchant endpoint is down for over 24 hours, Simpaisa SHOULD send an alert email to the merchant's technical contact

14.8 Webhook Testing

Provide a test endpoint or dashboard feature where merchants can trigger test webhook events to verify their integration.


15. Transaction Lifecycle & State Machines

Addresses: SA Audit Finding 20; GitBook Findings 19, 20

15.1 State Machine Documentation

Every product MUST document the full transaction state machine including:

  1. All possible states with human-readable names (not numeric codes like 0, 1)

  2. All valid state transitions (which state can transition to which)

  3. Which API calls trigger which transitions

  4. Which states are terminal (no further transitions)

  5. Which states trigger webhooks

  6. What the merchant should do at each state (wait, retry, contact support)

15.2 State Naming

Use UPPER_SNAKE_CASE for state values in API responses:

INITIATED → IN_PROCESS → COMPLETED
                       → FAILED → REVERSED
                       → ON_HOLD → COMPLETED (after balance top-up)
                                → REJECTED
           → STUCK → REJECTED (after max retries)
                   → COMPLETED (after manual intervention)
           → AML_REVIEW → COMPLETED
                        → REJECTED

Do NOT use numeric states (status = 0, status = 1) in merchant-facing APIs. If the internal system uses numeric states, map them to named states in the API layer.

15.3 SLA Language

Document transaction processing as SLAs, not implementation details:

  • Correct: "Disbursements are typically processed within 10 minutes of initiation"

  • Incorrect: "The scheduler runs every five minutes"

Never expose internal scheduler intervals, retry counts, or queue depths to merchants.


16. Field Validation & Data Types

Addresses: SA Audit Finding 18; GitBook Finding 21

16.1 Every Field Needs Validation Rules

Every request field in the OpenAPI spec MUST include:

Property Description Example
type JSON type string, number, integer, boolean
required Whether the field is required true / false
minLength / maxLength String length bounds 1 / 45
pattern Regex for format validation ^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$ (IBAN)
enum Allowed values ["PKR", "USD", "GBP", "BDT"]
minimum / maximum Numeric bounds 1.00 / 5000000.00
format Standard format date-time, email, uri, uuid
description What the field is and how to use it
example Representative value "03001234567"
default Default value if omitted (optional fields only)

16.2 Common Field Standards

Field Type Format Validation Notes
amount string Decimal ^[0-9]+\.[0-9]{2}$, min 0.01 String to preserve decimal precision. Always 2 decimal places
currency string ISO 4217 ^[A-Z]{3}$, enum of supported currencies e.g., PKR, USD, BDT
msisdn string E.164 ^\+[1-9]\d{6,14}$ Include country code: +923001234567
iban string ISO 13616 ^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$ e.g., PK36SCBL0000001123456702
reference string maxLength 128, ^[a-zA-Z0-9\-_]+$ Merchant-provided unique reference
transactionId string Assigned by Simpaisa Read-only in responses
merchantId string Assigned by Simpaisa Validated against auth
email string email RFC 5322
timestamps string date-time ISO 8601 UTC 2026-04-01T12:00:00Z

16.3 Enum Documentation

All enum fields MUST list every allowed value with a description. Do not scatter enum values across multiple documentation pages — define them in one place and reference them.

Example: transactionType values must be named, not numeric:

Value Description
WALLET_DEBIT Debit from customer wallet
WALLET_CREDIT Credit to customer wallet
BANK_TRANSFER Bank account transfer
CARD_PAYMENT Card payment

Not: 0, 1, 8, 9.


17. FX Rates & Currency Handling

Addresses: SA Audit Finding 17; GitBook Finding 18

17.1 FX Rate Quote Flow

The complete FX flow MUST be documented end-to-end:

1. GET  /v1/remittances/{merchantId}/fx-rates?from=USD&to=PKR&amount=100.00
   → Returns: rate, convertedAmount, fees, quoteId, expiresAt

2. POST /v1/remittances/{merchantId}/quotes/{quoteId}/confirm
   → Locks the rate. Returns: confirmedQuoteId

3. POST /v1/remittances/{merchantId}/transactions/initiate
   Body: { "quoteId": "confirmed-quote-id", ... }
   → Creates the remittance using the locked rate

17.2 Quote Validity

  • Document the quote validity window (e.g., 30 seconds, 2 minutes, 5 minutes)

  • The expiresAt timestamp MUST be included in the quote response

  • Using an expired quote MUST return 422 with error code QUOTE_EXPIRED and a message suggesting the merchant request a new quote

  • Do NOT silently re-quote at a different rate

17.3 Decimal Precision

Type Precision Example
Transaction amounts 2 decimal places 50000.00
FX rates 6 decimal places 278.543210
Fee amounts 2 decimal places 150.00

17.4 Currency Codes

Use ISO 4217 three-letter currency codes. Document all supported currencies with their constraints (min/max amounts, supported corridors).


18. OTP & Multi-Factor Authentication Flows

Addresses: SA Audit Finding 16; GitBook Findings 16, 17

18.1 OTP Specification

Property Standard
OTP length 4-6 digits (document per wallet provider)
OTP expiry 5 minutes maximum (not 30 minutes)
Max attempts 3 attempts before lockout
Lockout duration 30 minutes after max attempts exceeded
OTP transmission Request body only, NEVER in URL parameters
HTTP status on max attempts 429 with error code OTP_MAX_ATTEMPTS and Retry-After header

18.2 Non-OTP (Async Approval) Flows

For wallet flows that use push-to-approve (user approves in wallet app):

  • Document the approval window per provider (e.g., Easypaisa: 60s, JazzCash: 360s)

  • Document the polling endpoint and recommended polling interval

  • Document terminal states and what the merchant should do if approval times out

  • Document the webhook events triggered during this flow

18.3 Rate Limiting on OTP Endpoints

OTP initiation and verification endpoints MUST have aggressive rate limiting to prevent brute-force attacks:

  • OTP send: max 5 per phone number per hour

  • OTP verify: max 3 attempts per transaction (then lockout)


19. Bulk & Batch Operations

Addresses: SA Audit Finding 25

19.1 Batch Endpoint Pattern

For high-volume use cases (payroll, mass disbursements), provide batch endpoints:

POST /v1/disbursements/{merchantId}/batches

19.2 Batch Specification

Property Standard
Max batch size 1,000 items per request
Processing Asynchronous — return 202 Accepted with a batchId
Status tracking GET /v1/disbursements/{merchantId}/batches/{batchId}
Item-level status Each item in the batch has its own status
Partial failure Some items may succeed while others fail
Webhooks batch.completed event when all items are processed

19.3 Batch Response

{
  "data": {
    "batchId": "BATCH-2026-04-01-001",
    "status": "PROCESSING",
    "totalItems": 500,
    "processedItems": 0,
    "succeededItems": 0,
    "failedItems": 0,
    "createdAt": "2026-04-01T12:00:00Z"
  },
  "traceId": "abc-123-def-456"
}

20. Reconciliation & Settlement

Addresses: GitBook Finding 14

20.1 Transaction Identity

Document a clear mapping of transaction identifiers across the lifecycle:

Identifier Source Purpose
reference Merchant-provided Merchant's unique reference for reconciliation
transactionId Simpaisa-generated Simpaisa's internal transaction identifier
channelTransactionId Payment channel The ID from Easypaisa, 1Link, etc.

All three identifiers MUST be returned in the transaction response and included in webhook payloads so merchants can reconcile across systems.

20.2 Settlement Timing

Document settlement timing per channel:

Channel Settlement
Easypaisa Real-time
JazzCash Real-time
1Link (IBFT) Real-time
1Link (non-IBFT) T+1
Bank transfer T+1 to T+2

20.3 Reconciliation API

Provide a transaction search/export endpoint for reconciliation:

GET /v1/disbursements/{merchantId}/transactions?dateFrom=2026-04-01&dateTo=2026-04-01&status=COMPLETED&limit=100

Consider supporting CSV export for bulk reconciliation: Accept: text/csv.

20.4 Dispute Handling

Document the dispute process: how merchants report missing funds, investigation SLAs, and reversal procedures.


21. Sandbox & Testing

Addresses: SA Audit Finding 14; GitBook Finding 22

21.1 Sandbox Environment

Property Value
Base URL https://sandbox.simpaisa.com
Auth Same mechanism as production, with sandbox-specific credentials
Real money No — sandbox never moves real money
Channel behavior Simulated (does not hit real Easypaisa/JazzCash/1Link)
Data persistence Sandbox data may be reset periodically

21.2 Test Credentials

Provide test merchant credentials in the documentation:

  • Test merchantId

  • Test RSA key pair

  • Test webhook URL configuration

21.3 Test Scenarios (Magic Values)

Provide specific test values that simulate deterministic outcomes:

Test MSISDN / Account Simulated Outcome
+923000000001 Success — transaction completed
+923000000002 Insufficient balance
+923000000003 Invalid account
+923000000004 Timeout (channel does not respond)
+923000000005 Channel unavailable
+923000000006 Delayed success (webhook arrives after 30s)
Amount 99999.99 Amount exceeds limit

21.4 Webhook Testing

Provide a mechanism (API endpoint or dashboard button) for merchants to trigger test webhook events against their configured callback URL.


22. Transport Security

Addresses: SA Audit Finding 22

22.1 TLS Requirements

Requirement Standard
Minimum TLS version TLS 1.2
Recommended TLS version TLS 1.3
TLS 1.0 / 1.1 Not supported. Connections will be rejected
Cipher suites Follow Mozilla "Modern" or "Intermediate" configuration

22.2 Mutual TLS (mTLS)

  • Required for: Pay-Outs, Remittances, Cards (server-to-server)

  • Optional for: Pay-Ins (due to browser-originated flows)

  • Document certificate provisioning, format (PEM), and renewal process

22.3 Certificate Pinning

For mobile SDK integrations, provide certificate pinning guidance:

  • Public key pins (not certificate pins, for easier rotation)

  • Backup pins

  • Pin rotation process


23. PII Handling & Data Retention

Addresses: SA Audit Finding 23

23.1 PII in API Responses

Sensitive fields MUST be masked in API responses where full values are not necessary:

Field Type Masking Example
Account number Last 4 digits ****1234
MSISDN Last 4 digits +92300***4567
IBAN First 4 + last 4 PK36****6702
Name Not masked (needed for verification) Ali Khan

23.2 PII in Logs

  • Full account numbers, IBANs, MSISDNs, and credentials MUST NEVER appear in application logs

  • traceId should be used for debugging, not PII

  • Log masking must be enforced at the logging framework level, not per-developer discretion

23.3 Data Retention

Data Type Retention Period Basis
Transaction records 7 years Regulatory requirement
API request/response logs 90 days Debugging and dispute resolution
Idempotency keys 48 hours Operational
Webhook delivery logs 30 days Debugging

23.4 Data Deletion

Document whether merchants can request deletion or anonymization of PII, and the process for doing so.


24. Health & Status Endpoints

Addresses: SA Audit Finding 24

24.1 Health Check

GET /health

Returns 200 OK with:

{
  "status": "healthy",
  "timestamp": "2026-04-01T12:00:00Z"
}

This endpoint does NOT require authentication. It is used by load balancers, monitoring systems, and merchants checking availability.

24.2 Channel Status

GET /v1/status/channels

Returns operational status of payment channels:

{
  "data": {
    "channels": [
      { "name": "easypaisa", "status": "operational" },
      { "name": "jazzcash", "status": "operational" },
      { "name": "1link", "status": "degraded", "message": "Elevated latency" },
      { "name": "ubl", "status": "down", "since": "2026-04-01T11:30:00Z" }
    ]
  },
  "timestamp": "2026-04-01T12:00:00Z"
}

24.3 Public Status Page

Publish a status page (e.g., status.simpaisa.com) for real-time operational visibility. Include historical uptime and incident reports.


25. Documentation Separation

Addresses: SA Audit Finding 3; GitBook cross-cutting

25.1 Two-Tier Documentation

Tier Audience Contents Never Include
Merchant Integration Guide External merchants Endpoints, auth, schemas, error codes, webhooks, SDKs, test scenarios Database tables, queue names, scheduler intervals, cache config, internal architecture
Solution Architecture Internal engineering Infrastructure, database schema, scheduler design, retry logic, deployment

25.2 Rules

  • Merchant-facing documentation MUST NEVER reference: database table names, internal service names, scheduler intervals, cache layers, queue topics, or infrastructure components

  • Replace implementation language with contract language:

    • Incorrect: "Transaction is logged in the transaction table with status = 0"

    • Correct: "Transaction is created with status INITIATED"

    • Incorrect: "The scheduler runs every five minutes to process pending disbursements"

    • Correct: "Disbursements are typically processed within 10 minutes of initiation"


26. Naming Conventions

Addresses: SA Audit Finding 11; GitBook Findings 8, 10

26.1 Cross-Product Terminology

The same concept MUST use the same name across all products:

Concept Standard Name Not
Start a payment initiate remit-initiate, create, submit
Check status status inquire, verify, fetch
Get account info accounts/{id} fetch-account
Get balance balance balance-data
List banks banks banks/list
List failure reasons reasons reasons/list

26.2 URL Naming Rules

  • kebab-case for URL paths: /fx-rates, /fund-transfers, /title-fetch

  • camelCase for JSON field names: transactionId, merchantId

  • No camelCase in URLs: /getFxRate/fx-rates

  • No /list suffix: GET on a collection already implies listing

  • Resources are plural nouns: /transactions, /banks, /reasons

26.3 Header Naming

  • Standard headers use the HTTP convention: X-Merchant-Id, X-Timestamp, X-Signature

  • Product-specific mode headers (e.g., mode: remitance) should be consolidated into path-based product differentiation

  • Fix known typos: remitanceremittance (if the API enforces the misspelled value, accept both during migration)


27. Content Negotiation

Addresses: GitBook Finding 25

27.1 Supported Content Types

Direction Content-Type Required
Request application/json Yes
Response application/json Yes (default)
Response text/csv Optional (reconciliation endpoints)

27.2 Rules

  • JSON is the only required content type

  • Do not advertise text/plain or application/*+json in Accept headers unless the API actually supports them

  • Requests with unsupported Content-Type MUST return 415 Unsupported Media Type

  • Requests with unsupported Accept MUST return 406 Not Acceptable


28. Changelog & Deprecation

Addresses: SA Audit Finding 21; GitBook Finding 23

28.1 API Changelog

Maintain a public API changelog accessible to all merchants. Each entry includes:

  • Date

  • API version affected

  • Type: added, changed, deprecated, removed, fixed, security

  • Description of the change

  • Migration steps (if breaking)

28.2 Merchant Notification

Change Type Notification Lead Time
Breaking change Email + changelog + Sunset header 6 months minimum
New feature Changelog + developer portal announcement Immediate
Security fix Email + changelog Immediate
Deprecation Email + changelog + Sunset header in responses 6 months before removal

28.3 Sunset Header

Deprecated API versions MUST include the Sunset response header:

Sunset: Wed, 01 Oct 2026 00:00:00 GMT

29. API Design Checklist

Use this checklist when designing or reviewing any Simpaisa API endpoint:

Design & URL

  • Endpoint uses correct HTTP method (GET for reads, POST for creates, PUT/PATCH for updates, DELETE for deletes)
  • URL follows the standard pattern: /v{version}/{product}/{merchantId}/{resource}
  • URL uses kebab-case, plural nouns, no /list suffix
  • merchantId in path, validated against authenticated merchant

Authentication & Security

  • Authentication documented (header names, signature algorithm, code samples)
  • TLS 1.2+ required
  • PII masked in responses where applicable
  • No internal implementation details leaked

Request & Response

  • Request schema documented (all fields: name, type, required/optional, constraints, regex, min/max, enum)
  • Response schema documented for success and all error cases
  • HTTP status codes documented per endpoint
  • Error response follows standard schema with machine-readable error code
  • traceId included in every response

Reliability

  • Idempotency key supported for state-changing operations
  • Pagination supported for list endpoints (with page, limit, totalCount, hasNext)
  • Rate limits documented with response headers
  • Retry guidance documented (which errors are retryable)

Async & Webhooks

  • Webhook payload documented with all fields
  • Webhook signature verification documented
  • Webhook retry policy documented
  • Transaction state machine documented with webhook triggers
  • States that do NOT trigger webhooks are explicitly listed with polling guidance

Developer Experience

  • Curl example provided (success case and one error case)
  • Sandbox test scenario provided (magic values for deterministic testing)
  • Field validation rules specified (max length, regex, enum values)
  • All enum values documented with descriptions

Operational

  • API version in URL path
  • Deprecation policy followed for breaking changes
  • Changelog entry created for any change

30. Testing & Compliance

30.1 Layer 1: OpenAPI Spec Linting (CI)

  • Run Spectral against all OpenAPI spec files on every PR

  • Custom Spectral ruleset enforcing these standards (idempotency headers, error schema, pagination params, rate limit headers, etc.)

  • Blocks merge if spec violates rules

30.2 Layer 2: Contract Tests

For each API product, contract tests verify actual HTTP responses match the OpenAPI spec:

  • Use Schemathesis, Dredd, or Prism to auto-generate test cases from the OpenAPI spec

  • Run against sandbox environments on every deployment

  • Catches behavioral drift between spec and implementation

30.3 Layer 3: Integration Test Suite

Key scenarios to cover:

Scenario Expected Behavior Products
Duplicate idempotency key, same params Return original response, HTTP 200 All
Duplicate idempotency key, different params HTTP 409 Conflict All
Invalid auth signature HTTP 401 with standard error body All
Expired timestamp HTTP 401 with TIMESTAMP_EXPIRED All
merchantId mismatch HTTP 403 with MERCHANT_MISMATCH All
Exceeded rate limit HTTP 429 with Retry-After header All
Pagination: page beyond range Empty data array, correct pagination metadata All list endpoints
Webhook delivery failure Retry per policy, then mark failed All
FX rate quote expiry HTTP 422 with QUOTE_EXPIRED Remittances
OTP max attempts exceeded HTTP 429 with lockout duration Pay-Ins
Malformed JSON HTTP 400 with INVALID_REQUEST All
Unsupported Content-Type HTTP 415 All

30.4 Compliance Review Cadence

  • Every new endpoint: Must pass the API Design Checklist (Section 29) before merge

  • Quarterly: Audit all endpoints against these standards, track compliance percentage

  • Annually: Full re-audit comparable to the initial API Best Practices Audit


Appendix: Audit Findings Cross-Reference

This table maps every finding from the API Best Practices Audit (SA Docs) and the GitBook API Documentation Audit to the standard that addresses it.

SA Docs Audit (25 Findings)

# Finding Severity Standard Section
1 No idempotency keys CRITICAL 11. Idempotency
2 No standard error format CRITICAL 9. Error Handling
3 Internal details in merchant docs CRITICAL 25. Documentation Separation
4 No webhook specification CRITICAL 14. Webhooks & Async Notifications
5 No authentication flow documentation CRITICAL 6. Authentication & Security
6 No HTTP status codes CRITICAL 10. HTTP Status Codes
7 Inconsistent URL patterns HIGH 3. URL Design
8 POST used for read operations HIGH 4. HTTP Methods
9 No pagination HIGH 12. Pagination
10 No request/response schemas HIGH 2. OpenAPI Specification
11 Naming inconsistencies HIGH 26. Naming Conventions
12 No rate limiting documentation HIGH 13. Rate Limiting
13 Versioning strategy undefined HIGH 5. Versioning
14 No sandbox documentation HIGH 21. Sandbox & Testing
15 Scheduler timing as implicit SLA MEDIUM 15. Transaction Lifecycle, 25. Documentation Separation
16 OTP security hardening gaps MEDIUM 18. OTP & MFA Flows
17 FX rate handling underspecified MEDIUM 17. FX Rates & Currency Handling
18 No field-level validation rules MEDIUM 16. Field Validation & Data Types
19 Payout microservice inconsistency MEDIUM 26. Naming Conventions
20 Missing transaction lifecycle docs LOW 15. Transaction Lifecycle
21 No API changelog/migration guide LOW 28. Changelog & Deprecation
22 No TLS/mTLS requirements documented MEDIUM 22. Transport Security
23 No PII handling/data retention docs MEDIUM 23. PII Handling & Data Retention
24 No health check/status endpoint LOW 24. Health & Status Endpoints
25 No bulk/batch operation support LOW 19. Bulk & Batch Operations

GitBook Audit (28 Findings)

# Finding Severity Standard Section
1 Auth differs per product, no unified strategy CRITICAL 6. Authentication & Security
2 Idempotency only on Pay-Ins verify CRITICAL 11. Idempotency
3 No standard error response schema CRITICAL 9. Error Handling
4 No HTTP status codes documented CRITICAL 10. HTTP Status Codes
5 Webhook missing retry policy and signature CRITICAL 14. Webhooks & Async Notifications
6 Pay-Ins response schema incomplete HIGH 2. OpenAPI Specification
7 Pay-Outs missing response schemas HIGH 2. OpenAPI Specification
8 Inconsistent URL patterns HIGH 3. URL Design
9 7 different base URLs HIGH 3. URL Design
10 Header typo: remitance HIGH 26. Naming Conventions
11 No pagination for list endpoints HIGH 12. Pagination
12 No rate limiting documentation HIGH 13. Rate Limiting
13 Cards references 7 APIs without full docs HIGH 2. OpenAPI Specification
14 No reconciliation or settlement docs HIGH 20. Reconciliation & Settlement
15 Inconsistent request body wrapping MEDIUM 7. Request Design
16 OTP expiry not documented MEDIUM 18. OTP & MFA Flows
17 Non-OTP flow lacks API details MEDIUM 18. OTP & MFA Flows
18 FX rate quote validity not documented MEDIUM 17. FX Rates & Currency Handling
19 Disbursement states missing transition diagram MEDIUM 15. Transaction Lifecycle
20 Remittance postbacks only for 4 of 9 states MEDIUM 14. Webhooks & Async Notifications
21 Field validation rules incomplete MEDIUM 16. Field Validation & Data Types
22 Sandbox has no test scenarios MEDIUM 21. Sandbox & Testing
23 No API versioning strategy MEDIUM 5. Versioning
24 Typos in page titles LOW 26. Naming Conventions
25 Content negotiation headers unclear LOW 27. Content Negotiation
26 Bangladesh/Nepal/Iraq not assessed LOW Scope: apply all sections to every region
27 Refund API incomplete documentation HIGH 2. OpenAPI Specification
28 1 Bill and Hosted Page not assessed LOW Scope: apply all sections to every product

This document is version-controlled. Changes require CDO approval and must be announced via the API Changelog.