Webhook
资金一动,即刻知晓
收款、付款、虚拟账户、订阅、换汇和电汇的实时通知,每一条都以 HMAC-SHA256 签名,让您在处理前验证每一个请求。
- HMAC-SHA256 签名
- 指数退避重试
- 按国家和账户配置
- 09:41:02.11409:41:02PayIn100 INPROGRESS20084 ms
- 09:41:05.90209:41:05PayIn200 COMPLETED20091 ms
- 09:42:18.33009:42:18PayOut100 INPROGRESS20077 ms
- 09:42:40.01809:42:40PayOut200 COMPLETED20088 ms
- 09:43:00.55109:43:00VirtualAccount200 COMPLETED20080 ms
POST https://example-merchant.com/webhooks/localpayment
x-Signature: HMAC-SHA256 3f9a1c0e…b47e2b
{ "transactionType": "PayIn", "transactionFlow": "DIRECT",
"data": { "externalId": "order_8F2K-1043",
"status": { "code": "200", "description": "COMPLETED" } } }配置
在 Business Portal 中配置通知
登记端点无需编写代码。每个条目都可以按国家和账户限定范围。
- 01
打开通知设置
在 Business Portal 中进入 Configurations → Clients → Notifications。
- 02
添加 HTTPS URL
登记您服务器上用于接收事件的公网 HTTPS 端点。
- 03
限定范围
选择国家(或 All)以及通知所属的账户。
- 04
添加请求头并测试
可按需为您的网关添加自定义请求头,然后在 Stage 发起一笔测试交易。
载荷
所有事件共用一个信封结构
所有通知都共享相同的根字段,一个处理程序即可完成路由。请求体为 JSON,以 Content-Type text/plain 发送。
- transactionType:PayIn、PayOut、VirtualAccount、Subscription、CurrencyExchange、WireIn 或 WireOut
- transactionFlow:处理流程,便于路由
- data:交易本身,包含 externalId、internalId 和 status
事件
您会收到哪些通知
银行卡收款
- transactionType
- PayIn
- 通知内容
- 授权成功、完成、拒绝、退款或拒付
银行转账及本地支付方式收款
- transactionType
- PayIn
- 通知内容
- 付款人在银行或钱包应用中确认后的最终状态
虚拟账户
- transactionType
- VirtualAccount
- 通知内容
- 账户创建,以及每笔到账
订阅
- transactionType
- Subscription
- 通知内容
- 订阅生命周期;每次扣款还会以 PayIn 事件单独通知
付款
- transactionType
- PayOut
- 通知内容
- 付款流程中直至完成的各个状态变化
换汇
- transactionType
- CurrencyExchange
- 通知内容
- 您的余额之间换汇的结果
电汇入账(Wire In)
- transactionType
- WireIn
- 通知内容
- 资金已入账,可为法币或 USDC/USDT
电汇出账(Wire Out)
- transactionType
- WireOut
- 通知内容
- 提现已发出;加密货币出账包含区块链交易哈希
各事件的完整结构和状态码见 docs.localpayment.com 的 Webhook 参考。
安全
验证每一个签名
每个请求都带有 x-Signature: HMAC-SHA256 加原始请求体的十六进制摘要,由您的 Webhook 密钥计算得出。请重新计算并比对,然后再信任载荷中的任何内容。
- 读取原始请求体:它是以 Content-Type text/plain 发送的 JSON
- 去掉 “HMAC-SHA256 ” 前缀,再用恒定时间函数比较
- 签名不符时返回 401,切勿记录完整载荷
// 用 Express 接收 Localpayment Webhook(Node.js 18+)。
import crypto from 'node:crypto';
import express from 'express';
import { jobQueue } from './queue.js'; // 您自己的异步任务队列
const app = express();
// 启动时从密钥管理服务读取 Webhook 密钥,切勿硬编码。
const WEBHOOK_SECRET = process.env.LP_WEBHOOK_SECRET;
if (!WEBHOOK_SECRET) throw new Error('LP_WEBHOOK_SECRET is not configured');
function hasValidSignature(rawBody, header) {
if (typeof rawBody !== 'string') return false;
// 请求头格式:"x-Signature: HMAC-SHA256 <hex>",先去掉前缀。
const received = String(header ?? '').replace(/^HMAC-SHA256\s+/i, '').trim();
if (!/^[0-9a-f]{64}$/i.test(received)) return false;
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET).update(rawBody, 'utf8').digest();
return crypto.timingSafeEqual(Buffer.from(received, 'hex'), expected);
}
// 请求体是以 Content-Type text/plain 发送的 JSON:计算 HMAC 时请使用原始文本。
app.post('/webhooks/localpayment', express.text({ type: '*/*', limit: '1mb' }), (req, res) => {
if (!hasValidSignature(req.body, req.get('x-signature'))) return res.sendStatus(401);
let event;
try {
event = JSON.parse(req.body);
} catch {
return res.sendStatus(400);
}
res.sendStatus(200); // 先快速确认,再做耗时处理
// 异步处理。按 internalId/externalId + 状态去重,
// 确保重试投递只生效一次。切勿记录完整载荷。
jobQueue.enqueue(event);
});可靠处理
为重试和真实网络环境而设计
快速确认
签名验证通过后立即返回 200,耗时处理放到后台任务中完成。
预期会有重试
如果没有收到 200,我们会按指数退避重试。您的处理程序必须能安全地多次执行。
去重
以 internalId 或 externalId 加状态作为处理键,确保重试投递只生效一次。
不要假设顺序
应用前先将收到的状态与已存储的状态比较,绝不让交易状态倒退。
通过 API 确认
如有疑问,调用 GET /api/transactions/{externalId}/status 查询交易的当前状态。
安全记录日志
只记录事件类型、ID 和状态,绝不记录完整载荷、令牌或个人数据。
运营
在 Business Portal 中查看每次投递
通知报告会显示向您的端点发送了哪些内容,支持和工程团队无需翻查服务器日志即可定位问题。
- 发送到您端点的通知投递记录
- 交易搜索和详细的活动报告
- Webhook、IP 白名单和用户集中管理
概览
USD
1,284,560.00
+4.2%
BRL
3,420,118.40
+12.8%
MXN
8,905,332.10
+6.1%
- 收款 · Pixorder_8F2K-1043+ R$ 1,250.00
- 付款 · SPEIseller_0921− MX$ 18,400.00
- 换汇 · BRL → USDfx_3b7q250,000.00 USD
- 收款 · Bre-Border_9A1C-2210+ COP 890,000
Webhook 常见问题
如何确认 Webhook 确实来自 Localpayment?
验证 x-Signature 请求头:用您的 Webhook 密钥重新计算原始请求体的 HMAC-SHA256,并用恒定时间函数进行比较。您还可以为每个通知条目添加自定义请求头,供网关使用。
投递失败后多久重试一次?
未收到 200 的投递会按指数退避重试。请按多次投递来设计处理程序,并在 Business Portal 的通知报告中查看投递历史。
为什么 Content-Type 是 text/plain?
请求体是以 text/plain 发送的 JSON。请按原始文本读取,基于这些原始字节验证签名,然后再解析。
可以把不同国家的通知发到不同端点吗?
可以。每个通知条目都按国家(或 All)和账户限定范围,您可以把不同市场或业务线路由到不同的 URL。
我的端点应该返回什么?
尽快返回 200 状态,并在任何耗时处理之前返回。未收到 200 时,该次投递视为失败并会重试。

HMAC-SHA256 签名