Integration Flow Overview
High-level flow between the Bank / Acquirer and the Kesles Merchant platform.
Architecture
Bank / Acquirer Kesles Merchant
│ │
│ GET /api/psp/v1/merchants/ │
│ by-nmid/{nmid} ──────────────> │ verify HMAC (X-API-Key-ID)
│ │ filter by bank_code
│ <── 200 { merchant: {...} } ───── │
│ │
│ POST /psp/v1/events ─────────> │ verify HMAC (X-PSP-Key-ID)
│ { external_event_id, event_type, │ idempotency check
│ nmid, gross_amount, ... } │ route Flow A or B
│ │
│ (merchant lifecycle webhook) │
│ <── POST /your-webhook-url ────── │ merchant approved /
│ │ suspended / terminated
Service & Port Map
| Service | Port | Endpoint prefix | Use |
|---|---|---|---|
dashboard_api | 8082 | /api/psp/v1/merchants/* | Merchant lookup (read-only) |
payment-service | 8085 | /psp/v1/events · /psp/v1/settlements | Payment event receiver |
Flow 1 — Merchant Lookup (Outbound from Bank)
The bank receives a QRIS transaction → needs merchant data from Kesles based on NMID.
Steps:
- Bank receives the NMID from the transaction
- Bank calls
GET /api/psp/v1/merchants/by-nmid/{nmid} - Kesles verifies HMAC (
X-API-Key-ID/X-Timestamp/X-Signature) + IP allowlist - Kesles returns the merchant data (only merchants whose bank_code matches)
- Bank uses the data to process the transaction / settlement
Bank Kesles Merchant (dashboard_api:8082)
│ │
├─ GET /by-nmid/ID102326873XXXX ──────> │
│ X-API-Key-ID: key-id │ 1. verify HMAC
│ X-Timestamp: 1716452580 │ 2. check IP allowlist
│ X-Signature: hmac-sha256=... │ 3. filter bank_code = BMRI
│ X-Request-ID: uuid-v4 │ 4. return merchant data
│ │
│ <── 200 { merchant: { nmid, name, │
│ status, kyc_status, ... } } │
Performance SLA:
- p95
by-nmid< 50ms (indexed + Redis cache 30s) - p95
merchants/{id}< 100ms (Redis cache 30s)
Flow 2 — Forward Payment Event (Inbound to Kesles)
Successful transaction at the bank → PSP pushes the event to Kesles.
The events API (/psp/v1/events) uses a flat payload — not a nested envelope. Field external_event_id replaces the old event_id. See Events API for full schema.
Bank / PSP Kesles (payment-service:8085)
│ │
├─ POST /psp/v1/events ───────────────> │
│ X-PSP-Key-ID: key-id │ 1. verify HMAC
│ X-PSP-Timestamp: <rfc3339> │ 2. idempotency check
│ X-PSP-Signature: <hex,noprefix> │ 3. route Flow A or B
│ { │ 4. INSERT payment.transactions
│ "external_event_id": "psp-evt-xyz" │ 5. push notification (async)
│ "event_type": "transaction.success"│
│ "nmid": "ID102326873XXXX", │
│ "gross_amount": 75000, │
│ ...flat fields... │
│ } │
│ │
│ <── 200 { status: "success", flow: "A", merchant_id } ┤
Flow A vs Flow B
nmid value | Flow | Action |
|---|---|---|
| Merchant's NMID | A — customer paying merchant | INSERT payment.transactions + push notification to merchant |
ID9999999999999 (Kesles corporate) | B — merchant buying Kesles product | Notify order-service only |
Flow 3 — Delta Sync (Bulk Lookup)
For an initial load or reconciliation, the bank can periodically fetch all active merchants.
Bank Kesles Merchant
│ │
├─ GET /merchants?limit=500 ──────────> │
│ &updated_since=2026-05-20T00:00:00Z │ filter: bank_code + updated_since
│ │
│ <── 200 { items: [...], │
│ pagination: { │
│ next_cursor: "...", │
│ has_more: true } } │
│ │
├─ GET /merchants?cursor=... ──────────>│ (next page)
│ <── 200 { items: [...], │
│ pagination: { has_more: false } }
- Initial load: fetch all merchants once at go-live
- Delta sync: run every 5–15 minutes with
updated_since = last_sync_time - Per-transaction lookup: use
by-nmidfor real-time, not the/merchantslist
Flow 4 — Merchant Lifecycle Webhook (Inbound to Bank)
When a merchant's status changes in Kesles (approved / suspended / terminated), Kesles will send a webhook to the endpoint agreed on during onboarding.
Kesles Merchant Bank Webhook Endpoint
│ │
│ (merchant approved / suspended / terminated)│
│ │
├─ POST {your-webhook-url} ─────────────────> │
│ X-Kesles-Signature: sha256=... │ 1. verify signature
│ X-Kesles-Timestamp: unix_secs │ 2. update local state
│ X-Kesles-Event-ID: evt-uuid │
│ Content-Type: application/json │
│ { │
│ "event_id": "evt-uuid-v4", │
│ "event": "merchant.suspended", │
│ "merchant": { "nmid": "ID102...", ... } │
│ "change": { "reason": "kyc_expired" } │
│ } │
│ <── 200 OK ──────────────────────────────── │
See Events for the complete payload and event type list.
Retry Policy
For failed requests (network error, 5xx), implement:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 second |
| 3 | 5 seconds |
| 4 | 30 seconds |
| 5 | 2 minutes |
After 5 failed attempts, log the error and alert. Do not retry 4xx (except 429).