Skip to main content

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

ServicePortEndpoint prefixUse
dashboard_api8082/api/psp/v1/merchants/*Merchant lookup (read-only)
payment-service8085/psp/v1/events · /psp/v1/settlementsPayment event receiver

Flow 1 — Merchant Lookup (Outbound from Bank)

The bank receives a QRIS transaction → needs merchant data from Kesles based on NMID.

Steps:

  1. Bank receives the NMID from the transaction
  2. Bank calls GET /api/psp/v1/merchants/by-nmid/{nmid}
  3. Kesles verifies HMAC (X-API-Key-ID / X-Timestamp / X-Signature) + IP allowlist
  4. Kesles returns the merchant data (only merchants whose bank_code matches)
  5. 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.

Flat Payload Format

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 valueFlowAction
Merchant's NMIDA — customer paying merchantINSERT payment.transactions + push notification to merchant
ID9999999999999 (Kesles corporate)B — merchant buying Kesles productNotify 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 } }
Sync Recommendations
  • 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-nmid for real-time, not the /merchants list

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:

AttemptDelay
1Immediate
21 second
35 seconds
430 seconds
52 minutes

After 5 failed attempts, log the error and alert. Do not retry 4xx (except 429).