Payout notification
Payout notification. Sent once when the payout reaches 4 (success) or 5 (failed). Reply HTTP 200 {"status":"SUCCESS"} within 5 s.
See How callbacks work for retries and notify-URL rules.
Headers
| Header | Required | Value |
|---|---|---|
Content-Type | required | application/json |
lintaspay-signature | required | Base64 SHA256withRSA signature — verify it |
lintaspay-timestamp | required | Unix time in milliseconds |
lintaspay-nonce | required | Random string |
Body
| Name | Type | Description |
|---|---|---|
merchantId | integer | Example: 100001 |
mchOrderNo | string | Example: P202610140001 |
orderNo | string | Example: LPOUT2026101410300512AB34CD56EF |
amount | string | Example: 10000.00 |
fee | string | Example: 6500.00 |
orderStatus | integer | 4 success, 5 failed.Example: 4 |
completionTime | string | Example: 2026-10-14T10:42:28+07:00 |
failedReason | string |
Example
{
"merchantId": 100001,
"mchOrderNo": "P202610140001",
"orderNo": "LPOUT2026101410300512AB34CD56EF",
"amount": "10000.00",
"fee": "6500.00",
"orderStatus": 4,
"completionTime": "2026-10-14T10:42:28+07:00",
"failedReason": ""
}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\"}");
}