SIDIGI PAYMENT DOCS

Base URL: https://qris.ag1it.fun · Live from docs/INTEGRATION.md

Sidigi Payment Gateway — Integration Guide (IT)

Base URL: https://qris.ag1it.fun
Browser docs: https://qris.ag1it.fun/docs
Register (PIN): https://qris.ag1it.fun/portal/register.html
Merchant portal: https://qris.ag1it.fun/portal/
Health: https://qris.ag1it.fun/health

Ketentuan Layanan (Bahasa): https://qris.ag1it.fun/ketentuan.html
Settlement / pencairan: 2×24 jam (H+2) per transaksi setelah pembayaran berhasil.

Xendit Create QR Code reference: Create QR Code


Callback chain (important)

Merchant app                    Sidigi PG                         Xendit
     |                               |                                |
     |  POST /v1/payments/qris       |                                |
     |  (+ optional callback_url)    |                                |
     |------------------------------>|  POST /qr_codes                |
     |                               |  callback_url =                |
     |                               |  https://qris.ag1it.fun/      |
     |                               |    webhooks/xendit             |
     |                               |------------------------------->|
     |  qr_string (Tag 59 branded)   |  qr_string                     |
     |<------------------------------|<-------------------------------|
     |                               |                                |
     |                               |   payer scans & pays           |
     |                               |                                |
     |                               |  POST qr.payment               |
     |                               |  (x-callback-token)            |
     |                               |<-------------------------------|
     |  POST merchant callback_url   |  verify → mark paid            |
     |  (HMAC signed)                |                                |
     |<------------------------------|                                |
  1. Merchant → Sidigi — create QRIS; optionally pass your callback_url (or use the one saved on the merchant profile / portal).
  2. Sidigi → Xendit — always sets Create QR Code callback_url to our webhook: https://qris.ag1it.fun/webhooks/xendit.
  3. Xendit → Sidigi — event: qr.payment when paid; we verify x-callback-token.
  4. Sidigi → Merchant — after status is paid, we POST to your callback URL with X-Sidigi-Signature.

Merchants never talk to Xendit webhooks directly.


1. Roles

Role How you authenticate
Register (IT / merchant) Registration PIN on Register
Admin (Sidigi ops) X-Admin-Key: <ADMIN_API_KEY>
Merchant (backend) Authorization: Bearer sk_sidigi_...
Xendit POST /webhooks/xendit + x-callback-token

Self-register (PIN)

Open: https://qris.ag1it.fun/portal/register.html

Default PIN (change via REGISTER_PIN env): ask Sidigi ops (Sidigi123 on initial deploy).

What register does (only this):

  1. Validates PIN
  2. Creates merchant (code, name, password, callback_url)
  3. Returns api_key + callback_secret once

It does not set bank / disbursement details. Bank verification stays admin-only.

POST https://qris.ag1it.fun/register/merchants
Content-Type: application/json

{
  "pin": "Sidigi123",
  "code": "toko_rafsan",
  "name": "Toko Rafsan",
  "email": "[email protected]",
  "password": "your-portal-password",
  "callback_url": "https://merchant.example.com/webhooks/sidigi"
}

Merchant Portal (browser)

https://qris.ag1it.fun/portal/

Login with merchant code + portal password.


2. Admin: create a merchant (optional)

Admin can still create merchants without the PIN page:

POST https://qris.ag1it.fun/admin/merchants
X-Admin-Key: <ADMIN_API_KEY>
Content-Type: application/json

{
  "code": "toko_rafsan",
  "name": "Toko Rafsan",
  "email": "[email protected]",
  "callback_url": "https://merchant.example.com/webhooks/sidigi"
}

Response (credentials shown once):

{
  "success": true,
  "merchant": {
    "id": "uuid",
    "code": "toko_rafsan",
    "name": "Toko Rafsan",
    "callback_url": "https://merchant.example.com/webhooks/sidigi"
  },
  "credentials": {
    "api_key": "sk_sidigi_...",
    "callback_secret": "whsec_...",
    "portal_password": "..."
  }
}

Update default callback URL

PATCH https://qris.ag1it.fun/admin/merchants/{id}/callback
X-Admin-Key: <ADMIN_API_KEY>
Content-Type: application/json

{ "callback_url": "https://merchant.example.com/hooks/sidigi" }

Merchants can also change it in the portal.

Set / verify bank (once)

PUT https://qris.ag1it.fun/admin/merchants/{id}/bank
X-Admin-Key: <ADMIN_API_KEY>
Content-Type: application/json

{
  "bank_code": "BCA",
  "account_number": "1234567890",
  "account_name": "TOKO RAFSAN",
  "verify": true
}

3. Merchant: create a unique QRIS payment

Uses Xendit QR Codes V1 (POST /qr_codes, type DYNAMIC).
external_id must be unique per merchant.

POST https://qris.ag1it.fun/v1/payments/qris
Authorization: Bearer sk_sidigi_...
Content-Type: application/json

{
  "external_id": "ORDER-1001",
  "amount": 25000,
  "description": "Order 1001",
  "customer_name": "Budi",
  "callback_url": "https://merchant.example.com/webhooks/sidigi",
  "metadata": { "table": "A3" }
}
Field Required Notes
external_id yes Your order id
amount yes Min 1,500 / max 10,000,000 IDR
callback_url no Your webhook for this payment; else merchant profile callback
description / customer_name / metadata no

Success 201:

{
  "success": true,
  "payment": {
    "id": "uuid",
    "external_id": "ORDER-1001",
    "amount": 25000,
    "status": "requires_action",
    "merchant_display_name": "Toko Rafsan",
    "callback_url": "https://merchant.example.com/webhooks/sidigi",
    "qr_string": "000201...",
    "expires_at": "...",
    "xendit_qr_id": "qr_..."
  }
}

How to show the QR

  1. Render payment.qr_string as a QR image
  2. Do not alter the EMV string (Tag 59 is already set by Sidigi)
  3. Poll GET /v1/payments/{id} or wait for your callback

Status values

Status Meaning
pending / requires_action Waiting for payer
paid Confirmed via Xendit qr.payment
expired / failed Unpaid / failed

4. Merchant: get payment status

GET https://qris.ag1it.fun/v1/payments/{payment_id}
Authorization: Bearer sk_sidigi_...
GET https://qris.ag1it.fun/v1/payments
Authorization: Bearer sk_sidigi_...

5. Merchant callback (you implement this)

When Sidigi verifies Xendit’s qr.payment, we POST to your URL
(per-payment callback_url or merchant default).

Headers

Header Meaning
Content-Type application/json
X-Sidigi-Signature hex(HMAC-SHA256(callback_secret, raw_body))
X-Sidigi-Event e.g. payment.paid

Body example

{
  "event": "payment.paid",
  "merchant_code": "toko_rafsan",
  "merchant_name": "Toko Rafsan",
  "payment": {
    "id": "uuid",
    "external_id": "ORDER-1001",
    "amount": 25000,
    "status": "paid",
    "merchant_display_name": "Toko Rafsan",
    "paid_at": "..."
  },
  "sent_at": "..."
}

Verify signature (Node)

import { createHmac, timingSafeEqual } from "crypto";

function verify(rawBody, signatureHeader, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || "");
  return a.length === b.length && timingSafeEqual(a, b);
}

Rules

  1. Verify signature before trusting the event
  2. Idempotent on payment.id / external_id
  3. Respond 2xx quickly
  4. Fulfill only when event === "payment.paid" and status === "paid"
  5. Retry: POST /v1/payments/{id}/retry-callback

6. Payouts / disbursement

Access: only merchants with payout_enabled = true (admin-activated) can request disbursement.

POST https://qris.ag1it.fun/admin/merchants/{id}/payout-access
X-Admin-Key: ...
Content-Type: application/json

{ "enabled": true }

Settlement hold: each paid transaction becomes withdrawable 48 hours (H+2) after its own paid_at. Until then it shows as settling in the balance; next_release_at tells you when the oldest one matures. See Ketentuan Layanan.

Flow:

  1. Admin verifies the merchant bank account once, then activates disbursement (payout-access)
  2. Each transaction matures 48h after payment → moves from settling to available
  3. Merchant requests withdraw manually (POST /v1/payouts / portal) or enables auto disbursement
  4. Admin marks approved → processing → paid (or rejected)

Auto disbursement (every 3 days)

Toggle in the portal (Auto disbursement card), or:

PATCH https://qris.ag1it.fun/portal/api/auto-payout
Authorization: Bearer <portal session token>
Content-Type: application/json

{ "enabled": true }
POST https://qris.ag1it.fun/v1/payouts
Authorization: Bearer sk_sidigi_...
Content-Type: application/json

{ "amount": 100000, "note": "Weekly settlement" }

Admin:

GET https://qris.ag1it.fun/admin/payouts
X-Admin-Key: ...

POST https://qris.ag1it.fun/admin/payouts/{id}/status
X-Admin-Key: ...
Content-Type: application/json

{ "status": "paid", "admin_note": "Disbursed" }

Statuses: requested → approved → processing → paid | rejected | failed


7. Xendit → Sidigi webhook (infra only)

POST https://qris.ag1it.fun/webhooks/xendit
x-callback-token: <XENDIT_CALLBACK_TOKEN>

Sidigi sets this URL on every Create QR Code call (docs).

Mapped events:

Also set the same URL + verification token in Xendit Dashboard Callback Settings.


8. QR branding (Tag 59 + Tag 60 + Tag 61)

After the QR is created, Sidigi rewrites:

Tag Field Value
59 Merchant name / prefix Store name from portal or merchant profile (max 25 bytes)
60 Location / city Papua (QRIS_CITY)
61 Postal code 99332 (QRIS_POSTAL_CODE)

CRC (Tag 63) is recalculated. Change the store name in the portal; city and zip are fixed.


9. Error codes

HTTP Meaning
400 Validation / amount < 1500 / bank not verified
401 Bad admin or merchant key
403 Merchant suspended
409 Duplicate external_id
502 Xendit rejected create

10. Merchant IT go-live checklist