production

Pay-in notification

POSTyour notifyUrlLintasPay → your server

Pay-in notification. Sent once when the order reaches a final state. Reply HTTP 200 {"status":"SUCCESS"} within 5 s.

See How callbacks work for retries and notify-URL rules.

Headers

HeaderRequiredValue
Content-Typerequiredapplication/json
lintaspay-signaturerequiredBase64 SHA256withRSA signature — verify it
lintaspay-timestamprequiredUnix time in milliseconds
lintaspay-noncerequiredRandom string

Body

NameTypeDescription
amountstring
Example: 10000
completionTimestring
Example: 2026-10-14T10:35:12+07:00
feestring
Example: 4500.00
mchOrderNostring
Example: M202610140001
merchantIdinteger
Example: 100001
nonceStrstring
Example: a1b2c3d4e5f60718
orderNostring
Example: LPIN2026101410300512AB34CD56EF
orderStatusinteger2 success, 3 failed.
Example: 2
payCodestring
Example: 8808123456789012
productCodeinteger
Example: 11
settleDatestring
Example: 2026-10-15
additionalInfoobject | nullPayer details for a successful pay-in (when the channel provides them).
paymentNtbstring
Example: NTB20261014103512
senderNamestring
Example: BUDI SANTOSO
sourceAccountNostring
Example: 0123456789
sourceBankNamestring
Example: BNI

Example

{
  "amount": "10000",
  "completionTime": "2026-10-14T10:35:12+07:00",
  "fee": "4500.00",
  "mchOrderNo": "M202610140001",
  "merchantId": 100001,
  "nonceStr": "a1b2c3d4e5f60718",
  "orderNo": "LPIN2026101410300512AB34CD56EF",
  "orderStatus": 2,
  "payCode": "8808123456789012",
  "productCode": 11,
  "settleDate": "2026-10-15",
  "additionalInfo": {
    "paymentNtb": "NTB20261014103512",
    "senderName": "BUDI SANTOSO",
    "sourceAccountNo": "0123456789",
    "sourceBankName": "BNI"
  }
}

Your response

Reply HTTP 200 within 5 seconds with exactly:

{"status":"SUCCESS"}

Verifying the notification

// Express — keep the raw body for signature verification
import crypto from 'node:crypto';
import express from 'express';
import fs from 'node:fs';

const PLATFORM_PUBLIC_KEY = fs.readFileSync('lintaspay_public_key.pem', 'utf8');
const app = express();

app.post('/notify/payout', express.raw({ type: '*/*' }), (req, res) => {
  const raw = req.body.toString('utf8');
  const signed = `${req.get('lintaspay-timestamp')}\n${req.get('lintaspay-nonce')}\n${raw}\n`;
  const valid = crypto.verify('RSA-SHA256', Buffer.from(signed), PLATFORM_PUBLIC_KEY,
    Buffer.from(req.get('lintaspay-signature') ?? '', 'base64'));
  if (!valid) return res.status(401).end();

  const notice = JSON.parse(raw);
  queueForProcessing(notice);          // process asynchronously, idempotently (by orderNo)
  res.json({ status: 'SUCCESS' });     // reply within 5 seconds
});
# Flask
import base64, json
from flask import Flask, request, jsonify
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

with open("lintaspay_public_key.pem", "rb") as f:
    PLATFORM_PUBLIC_KEY = serialization.load_pem_public_key(f.read())
app = Flask(__name__)

@app.post("/notify/payout")
def notify_payout():
    raw = request.get_data(as_text=True)
    signed = f"{request.headers['lintaspay-timestamp']}\n{request.headers['lintaspay-nonce']}\n{raw}\n".encode()
    try:
        PLATFORM_PUBLIC_KEY.verify(base64.b64decode(request.headers["lintaspay-signature"]), signed,
                                   padding.PKCS1v15(), hashes.SHA256())
    except (InvalidSignature, KeyError):
        return "", 401
    queue_for_processing(json.loads(raw))   # process asynchronously, idempotently (by orderNo)
    return jsonify(status="SUCCESS")        # reply within 5 seconds
<?php
$raw = file_get_contents('php://input');
$signed = $_SERVER['HTTP_LINTASPAY_TIMESTAMP'] . "\n" . $_SERVER['HTTP_LINTASPAY_NONCE'] . "\n" . $raw . "\n";
$platformKey = openssl_pkey_get_public(file_get_contents('lintaspay_public_key.pem'));
$ok = openssl_verify($signed, base64_decode($_SERVER['HTTP_LINTASPAY_SIGNATURE'] ?? ''), $platformKey, OPENSSL_ALGO_SHA256);
if ($ok !== 1) {
    http_response_code(401);
    exit;
}
$notice = json_decode($raw, true);
queue_for_processing($notice);   // process asynchronously, idempotently (by orderNo)
header('Content-Type: application/json');
echo '{"status":"SUCCESS"}';     // reply within 5 seconds
// Spring Boot
@PostMapping(value = "/notify/payout", consumes = "*/*")
public ResponseEntity<String> notifyPayout(@RequestBody String raw,
        @RequestHeader("lintaspay-signature") String signature,
        @RequestHeader("lintaspay-timestamp") String timestamp,
        @RequestHeader("lintaspay-nonce") String nonce) throws Exception {
    Signature v = Signature.getInstance("SHA256withRSA");
    v.initVerify(platformKey); // PublicKey loaded from lintaspay_public_key.pem
    v.update((timestamp + "\n" + nonce + "\n" + raw + "\n").getBytes(StandardCharsets.UTF_8));
    if (!v.verify(Base64.getDecoder().decode(signature))) {
        return ResponseEntity.status(401).build();
    }
    queueForProcessing(raw);  // process asynchronously, idempotently (by orderNo)
    return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body("{\"status\":\"SUCCESS\"}");
}