Skip to main content

Merchant Lifecycle Events

Kesles sends webhooks to the bank's endpoint when a merchant's status changes. The bank must respond with 200 OK within 10 seconds.

Payment Event Webhooks (PSP → Kesles)

This page documents outbound webhooks from Kesles to the bank (merchant lifecycle). For the reverse direction — forwarding payment events from the bank to Kesles — see the Events API below or the Endpoint List.

Webhook Signature Verification

Every lifecycle webhook from Kesles to the bank includes these headers:

X-Kesles-Signature: sha256=<signature>
X-Kesles-Timestamp: <unix_seconds>
X-Kesles-Event-ID: <uuid — for idempotency>

Verify the same way as for outbound requests (see Integration Contract).

warning

Reject webhooks where |now - X-Kesles-Timestamp| > 300 seconds to prevent replay attacks.

Merchant Lifecycle Event Types

EventTrigger
merchant.activatedMerchant activated for the first time (KYC verified + NMID assigned)
merchant.suspendedMerchant suspended (KYC expired, violation, etc.)
merchant.reactivatedMerchant reactivated after suspension
merchant.terminatedMerchant permanently removed from the Kesles platform
merchant.profile_updatedName, address, or KYC data updated

Lifecycle Payload Format

All lifecycle events use the same envelope:

{
"event_id": "evt-uuid-v4",
"event": "merchant.activated",
"occurred_at": "2026-05-21T10:00:00Z",
"merchant": {
"id": "kesles-merchant-uuid",
"nmid": "ID102326873XXXX",
"bank_code": "BMRI",
"merchant_name": "Warung Pak Budi",
"legal_name": "CV Sumber Rejeki",
"status": "active",
"kyc_status": "verified",
"category_code": "5411",
"phone": "081234567890",
"address": {
"city": "Makassar",
"province": "Sulawesi Selatan",
"country": "ID"
},
"updated_at": "2026-05-21T10:00:00Z"
},
"change": {
"field": "status",
"old_value": "inactive",
"new_value": "active",
"reason": "kyc_verified"
}
}

merchant.suspended

{
"event_id": "evt-uuid-v4",
"event": "merchant.suspended",
"occurred_at": "2026-05-21T11:00:00Z",
"merchant": { "...": "same as above" },
"change": {
"field": "status",
"old_value": "active",
"new_value": "suspended",
"reason": "kyc_expired"
}
}

reason values for merchant.suspended:

ReasonDescription
kyc_expiredKYC documents expired
violationTerms of service violation
operator_actionManually suspended by a Kesles operator
inactivityNo transactions for 6 months

merchant.terminated

{
"event_id": "evt-uuid-v4",
"event": "merchant.terminated",
"occurred_at": "2026-05-21T12:00:00Z",
"merchant": {
"id": "kesles-merchant-uuid",
"nmid": "ID102326873XXXX",
"bank_code":"BMRI",
"status": "deleted"
},
"change": {
"field": "status",
"old_value": "active",
"new_value": "deleted",
"reason": "merchant_request"
}
}
Terminated Merchant

After merchant.terminated, the NMID is no longer valid. The bank must reject transactions with that NMID and remove it from the local cache.


Payment Events API (Bank → Kesles)

The PSP forwards payment events to Kesles via the payment-service at POST /psp/v1/events.

Auth Headers (Events API)

X-PSP-Key-ID: {key_id}
X-PSP-Timestamp: {RFC3339 timestamp — e.g. 2026-06-08T10:30:00+07:00}
X-PSP-Signature: {raw hex signature — no prefix}
X-Request-ID: {uuid}

Signed Payload Format

Events API signed_payload: {method}\n{path}\n{timestamp}\n{body}timestamp is the same RFC3339 value sent in X-PSP-Timestamp.

POST
/psp/v1/events
2026-06-08T10:30:00+07:00
{"external_event_id":"psp-evt-abc-123",...}

Payment Event Types

event_typeDescription
transaction.successNew successful transaction at the bank
transaction.failedTransaction attempt failed
transaction.cancelledTransaction cancelled before completion
transaction.refundedRefund of a previous transaction
transaction.reversedTransaction reversed / voided
qris.payment.successSuccessful QRIS payment
qris.payment.failedFailed QRIS payment
qris.payment.refundedRefunded QRIS payment
nmid.status_changedBank suspends or activates an NMID

transaction.success Payload

{
"external_event_id": "psp-evt-abc-123",
"event_type": "transaction.success",
"nmid": "ID102326873XXXX",
"gross_amount": 75000,
"transaction_code": "TRX-PSP-20260523-001",
"reference_number": "BI20260523142345XXXX",
"rrn": "301234567890",
"transaction_at": "2026-05-23T14:23:45Z",
"payer_bank_code": "014",
"payer_bank_name": "BCA",
"payer_name_masked": "Budi S.",
"mdr_fee_bank": 300,
"switching_fee": 150,
"platform_fee": 75,
"status": "success"
}

nmid.status_changed Payload

{
"external_event_id": "psp-evt-nmid-789",
"event_type": "nmid.status_changed",
"nmid": "ID102326873XXXX",
"nmid_new_status": "suspended",
"nmid_old_status": "active",
"nmid_reason": "fraud_detected",
"transaction_at": "2026-05-23T12:00:00Z"
}

Idempotency (Lifecycle Webhooks from Kesles)

Use event_id from X-Kesles-Event-ID header for deduplication. If the same webhook is received twice, process it once — return 200 OK for both.

Retry from Kesles

If the bank's endpoint does not respond with 200 within 10 seconds, Kesles will retry:

AttemptDelay
230 seconds
35 minutes
430 minutes
52 hours

After 5 failed attempts, the Kesles team will send a manual notification to the technical PIC.

Webhook Endpoint Registration

Register your webhook URL with the Kesles team during onboarding. The URL must:

  • Be HTTPS with a valid certificate
  • Respond with 200 OK within 10 seconds
  • Accept Content-Type: application/json