开发者参考

Peptide-Pay API

用于刷卡和加密货币收款的 REST API,结算到你掌控的 USDC 钱包。一个 POST 创建结账。一个 webhook 通知你已付款。30 分钟内上线。

REST · JSONBearer 认证HMAC-SHA256 webhookIdempotency-Key支持 CORS

开始使用

快速开始(5 分钟)

三步:创建会话、重定向客户、处理 webhook。下面的示例是生产级的 Node.js 结账路由。

app/checkout/route.ts
// Create a checkout session and redirect your customer.
// Authorization resolves the merchant wallet server-side — no wallet in the body.

const res = await fetch('https://peptide-pay.com/api/v1/checkout/init', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.PEPTIDEPAY_API_KEY}`, // sk_live_…
  },
  body: JSON.stringify({
    amount_cents: 5000,                       // €50.00 — integer, in cents
    currency: 'EUR',                          // EUR, USD, GBP, CAD, AUD, CHF
    email: 'buyer@example.com',               // optional, shown in checkout
    success_url: 'https://mystore.com/success',
    cancel_url:  'https://mystore.com/cart',
    webhook_url: 'https://mystore.com/api/peptidepay-webhook',
    metadata: { order_id: '1234' },
  }),
});

const { id, url, tracking_number } = await res.json();
// => { id: 'cs_abc…', url: 'https://peptide-pay.com/session/cs_abc…',
//      tracking_number: '0x…', provider: 'gateway', status: 'pending', … }

// Redirect your customer to the hosted checkout.
return Response.redirect(url, 303);

这就是整条主干流程。客户落在 的托管结账页,选银行卡或加密货币,你的 webhook 在付款 30 秒内触发。

认证

两种方案,取决于接入模式:

方案怎么用何时用
Bearer tokenAuthorization: Bearer sk_live_…服务端。钱包保持私密。
钱包放请求体{ "wallet": "0x…", … }无后端的静态站点 / widget。
注意
API 密钥带着商家账户的完整身份 —— 当密码对待。永不要提交到 Git、永不要发到浏览器。如果泄露,从 /app/api-keys 轮换。
提示
No sandbox mode. Signup returns both an sk_live_… and an sk_test_… key. Use sk_live_ as your canonical key — that is what every example here uses. The sk_test_ key is provided for the webhook-receiver simulator at /api/v1/test/fire-webhook. Peptide-Pay settles real on-chain USDC — there is no test network. To dry-run an integration, run a $1 real payment and refund yourself.

API 参考

Base URL:。所有接口使用 JSON,成功返回单个对象,4xx/5xx 返回 对象。

POSThttps://peptide-pay.com/api/v1/checkout/init

#创建结账会话

生成托管结账 URL。客户打开,用银行卡或加密货币付款,Peptide-Pay 以 USDC 结算到你的钱包,webhook 触发。

请求体
字段类型必填说明
amount_centsinteger必填以最小货币单位表示的金额(分)。范围 100 – 10 000 000。
currencystring必填ISO 4217 代码。支持:EUR、USD、GBP、CAD、AUD、CHF。
walletstring二选一Polygon 上的 USDC 钱包(0x + 40 位 hex)。通过 Bearer 密钥认证时非必填。
customer_emailstring选填显示在结账页 UI 上,并转发给法币通道用于 KYC 复用。
success_urlurl选填付款成功后重定向。仅 http/https。
cancel_urlurl选填客户放弃结账时重定向。
webhook_urlurl选填order.paid 事件的 POST 目标。覆盖后台默认值。
providerstring选填默认 'gateway'(智能选路 —— 推荐)。或从 GET /providers 指定一个具体的法币通道 id(例如 moonpay、revolut、banxa、transak)。
product_namestring选填结账页显示的标签(最多 80 字符)。
metadataobject选填最多 10 组 string key/value,会在 webhook 中原样回传。保留 key:order_id。
响应(200 OK)
字段类型必填说明
idstring选填会话 id,以 cs_ 开头。
urlstring选填重定向客户用的托管结账 URL。
statusstring选填创建时永远是 "pending"。
amountinteger选填amount_cents 的回显。
currencystring选填currency 的回显。
providerstring选填provider 的回显(默认 'gateway')。
expires_atstring选填ISO 8601 过期时间(创建后 24 小时)。
tracking_numberstring选填Polygon 结算地址 —— 匹配 webhook 负载中的 address_in,可用 /track 实时监控。

示例

// Node.js 18+
const res = await fetch('https://peptide-pay.com/api/v1/checkout/init', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.PEPTIDEPAY_API_KEY}`,
    'Idempotency-Key': crypto.randomUUID(),   // safe double-submit
  },
  body: JSON.stringify({
    amount_cents: 5000,
    currency: 'EUR',
    email: 'buyer@example.com',
    success_url: 'https://mystore.com/success',
    cancel_url:  'https://mystore.com/cart',
    metadata: { order_id: '1234' },
  }),
});

if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { id, url } = await res.json();
return Response.redirect(url, 303);
提示
传入 让重试安全。我们会在 24 小时内对同一 key 回放缓存响应,而不是生成新会话(防止网络波动导致重复扣款)。
GEThttps://peptide-pay.com/api/v1/sessions/{id}

#查询会话(轮询)

作为 webhook 降级方案,或重定向后用来渲染成功页。服务端每次调用都会回源结算层 —— 便宜(<200ms),每 3-5 秒轮询没问题。

响应
字段类型必填说明
idstring选填会话 id。
statusstring选填pending | paid | expired | failed。
amountinteger选填原始金额(分)。
currencystring选填原始币种。
paid_atstring|null选填ISO 8601,链上结算完成时间。
paid_providerstring|null选填实际处理付款的通道(可能和请求的不一样)。
txidstring|null选填Polygon 结算 txid。链接到 polygonscan.com/tx/{txid}。
expires_atstring选填ISO 8601 过期时间。
// Poll every 3-5 seconds until terminal state. Use webhooks for push-
// delivery in production; polling is the fallback when webhooks are down.
async function waitForPayment(sessionId, { timeoutMs = 15 * 60 * 1000 } = {}) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const res = await fetch(`https://peptide-pay.com/api/v1/sessions/${sessionId}`);
    const s = await res.json();
    if (s.status === 'paid')    return s;       // terminal: success
    if (s.status === 'expired') throw new Error('Session expired');
    if (s.status === 'failed')  throw new Error('Payment failed');
    await new Promise(r => setTimeout(r, 4000));
  }
  throw new Error('Polling timeout');
}
GEThttps://peptide-pay.com/api/v1/providers

#实时通道矩阵

列出当前接受流量的法币通道,以及每个通道的最低金额。边缘缓存 5 分钟 —— 应用启动时拉一次,不要每次请求都拉。

响应
字段类型必填说明
providers[].idstring选填通道 key(可以作为 `provider` 传给 /checkout/init)。
providers[].provider_namestring选填下拉菜单里显示给人看的名称。
providers[].statusstring选填'active'(此接口始终只返回 active)。
providers[].minimum_currencystring选填最低金额的 ISO 代码。
providers[].minimum_amountnumber选填通道接受的最低金额(以 minimum_currency 单位)。
curl -sS 'https://peptide-pay.com/api/v1/providers' | jq '.providers[] | {id, provider_name, minimum_currency, minimum_amount}'

# [
#   { "id": "gateway",  "provider_name": "Smart (recommended)",   "minimum_currency": "USD", "minimum_amount": 1 },
#   { "id": "moonpay",  "provider_name": "Moonpay",               "minimum_currency": "EUR", "minimum_amount": 20 },
#   { "id": "revolut",  "provider_name": "Revolut Ramp",          "minimum_currency": "EUR", "minimum_amount": 10 },
#   { "id": "binance",  "provider_name": "Binance Pay",           "minimum_currency": "EUR", "minimum_amount": 15 },
#   …
# ]
# Cache: 5 minutes at the edge. Call once per deploy, not per request.

Webhooks

当会话进入终态,我们会向你配置的 (按会话或在后台)POST 一个签名 JSON 事件。做签名验证时永远读取 请求体 —— 重新序列化 JSON 会打乱 key 顺序,HMAC 就失败了。

POST /your-endpoint  HTTP/1.1
Host: mystore.com
Content-Type: application/json
x-peptidepay-signature: t=1745300551,v1=3f9b5c1e8a7d…    ← HMAC-SHA256, hex

{
  "event":      "order.paid",
  "session_id": "cs_abc123",
  "order_id":   "1234",
  "address_in": "0xAb12…",
  "status":     "paid",
  "amount":     5000,
  "currency":   "EUR",
  "txid":       "0xfa89b2…",
  "paid_at":    "2026-04-23T10:02:31.000Z",
  "attempt":    1
}

事件类型

事件何时
order.paid链上结算已确认。 + + 一定存在。标记订单已付。

当前只推送 —— expired 和 failed 的会话可通过 观察(24 小时 TTL 后状态变为 ;终态失败显示 )。后续版本可能为这些加推送事件。

签名验证

已注册的商家会收到一个 secret,每次投递都带 头,格式为 。计算 ,用恒定时间比较 。拒绝超过 5 分钟的签名。

提示
仅钱包模式(没注册,没有 )的 webhook 是未签名的 —— 你仍应校验 匹配你创建的会话。要拿签名投递,到 /signup 注册拿 secret。
// Node.js — Express/Next.js route handler
import crypto from 'node:crypto';

const SECRET = process.env.PEPTIDEPAY_WEBHOOK_SECRET; // dashboard → Webhooks

export async function POST(req) {
  const rawBody = await req.text();                 // MUST be the raw bytes
  const header  = req.headers.get('x-peptidepay-signature') ?? '';
  const [ tPart, v1Part ] = header.split(',');
  const t  = tPart?.split('=')[1];
  const v1 = v1Part?.split('=')[1];
  if (!t || !v1) return new Response('bad sig', { status: 400 });

  // Reject replays older than 5 minutes.
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300)
    return new Response('stale', { status: 400 });

  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(`${t}.${rawBody}`)
    .digest('hex');

  const ok =
    v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'));
  if (!ok) return new Response('invalid sig', { status: 401 });

  const event = JSON.parse(rawBody);
  // Idempotency: dedupe by event.session_id in your DB — retries re-fire
  // the same event (with an incrementing "attempt" field) until you 2xx.
  if (event.event === 'order.paid') {
    await markOrderPaid(event.order_id, event.txid);
  }
  return new Response('ok');
}
注意
务必使用恒定时间比较:(Node)、(Python)、(PHP)、(Ruby)。普通 会按字节把 HMAC 泄露给时序攻击者。

重试策略

非 2xx 响应(以及 > 5 秒超时)我们会按指数退避重试。约 42 小时内总共 6 次:

  • 第 1 次 —— 确认后立即。
  • 第 2 次 —— +5 分钟。
  • 第 3 次 —— +15 分钟。
  • 第 4 次 —— +1 小时。
  • 第 5 次 —— +4 小时。
  • 第 6 次 —— +12 小时,然后 +24 小时(最后一次)。

6 次都失败后进入死信队列。你随时可以通过 重新请求当前状态。

提示
请把你的处理函数做成幂等的。按 去重 —— 重试可能重发你已经处理过的已付事件。

常见问题

每次投递都提示 '签名无效'
你的框架在你做 hash 前就把 body 解析成 JSON 了。读原始字节(Express:express.raw({type:'*/*'});Next.js:req.text();Laravel:request()->getContent();Rails:request.raw_post)。hash 前永不要重新序列化。
IP 白名单 —— 你们从哪些 IP 发?
目前投递来自 Vercel Edge(动态 IP)。我们不发布静态网段。如果你必须白名单,用签名头作为认证闸门,接受任意源 IP —— HMAC 才是真正的身份校验。
必须 HTTPS 吗?
是。我们拒绝 POST 到 http:// 接口(confusable deputy / 明文重放风险)。ngrok 免费的 https URL 本地测试足够。
我的接口很慢 —— 能延长 5 秒超时吗?
不能。立即 2xx 响应,然后异步处理(任务队列、setImmediate、goroutine)。长阻塞处理函数总会超时。

SDK

API 很小, 就完全够用 —— 但 Node SDK 给你类型、自动重试和一个帮你做签名验证的 辅助函数。

npm install github:kinerette/peptide-pay-sdk
// npm install github:kinerette/peptide-pay-sdk

import { PeptidePay } from 'peptide-pay';

const pp = new PeptidePay(process.env.PEPTIDEPAY_API_KEY);

// Create a session
const session = await pp.checkout.create({
  amount_cents: 5000,
  currency: 'EUR',
  customer_email: 'buyer@example.com',
  success_url: 'https://mystore.com/success',
  cancel_url:  'https://mystore.com/cart',
  metadata: { order_id: '1234' },
});

// Retrieve a session
const latest = await pp.sessions.retrieve(session.id);

// Verify + parse a webhook (throws on invalid signature)
app.post('/webhooks/peptidepay', express.raw({ type: '*/*' }), (req, res) => {
  const event = pp.webhooks.constructEvent(
    req.body,
    req.headers['x-peptidepay-signature'],
    process.env.PEPTIDEPAY_WEBHOOK_SECRET,
  );
  // event.event === 'order.paid' (currently the only event delivered)
  res.sendStatus(200);
});
Node / TypeScript稳定
peptide-pay

完整类型、webhook 辅助、自动重试。

直接 fetch()永远能用
任意语言

一个 POST,一个 GET。不需要库。

提示
Python、PHP、Ruby 和 Go SDK 在路线图上。发布之前,上面原生 // 示例就是权威参考 —— 我们不会破坏它们。

费率

统一 —— Peptide-Pay 的全部佣金。没有订阅、没有月费、没有拒付费。卡法币通道费(上游卡处理商收约 4.5%)是透传 —— 客户付,从不影响你的到账。

付款方式你付客户付
银行卡 / Apple Pay / Google Pay3%约 4.5%(法币通道,透传)
加密货币直接(USDC → USDC)3%仅 gas(Polygon 约 $0.01)

含示例的完整拆解见 /fees

测试

Peptide-Pay 没有沙箱网络 —— 每一笔结算都是真实的链上转账。风险最低的测试就是自己发起一笔 再退给自己。想不花钱端到端跑通 webhook,用下面的测试模拟器。

  • Webhook 模拟器:用 sk_test_ 密钥 POST 到 /api/v1/test/fire-webhook,就能向你的接口触发一个带签名、标记 的 order.paid —— 无需付款,也无需退款。
  • MoonPay 开发测试卡:,过期日期随便填,CVV 随便填,ZIP 10001。
  • 本地 webhook 测试:用 ngrok 暴露 localhost,把 URL 粘到会话的 字段。

完整本地回路(ngrok)

# Test your webhook receiver WITHOUT a real on-ramp payment.
# Do NOT test by paying a tiny amount: anything below an on-ramp's
# minimum (~$20) hits the gateway's "below minimum" error + KYC — that's
# the live on-ramp, not your webhook. Use the signed simulator instead.

# 1. Expose your local webhook endpoint
ngrok http 3000

# 2. Create a TEST session with your sk_test_ key. Amount can be anything
#    (the simulator ignores on-ramp minimums). Note the "id" it returns.
curl -X POST 'https://peptide-pay.com/api/v1/checkout/init' \
  -H "Authorization: Bearer $PEPTIDEPAY_TEST_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "amount_cents": 5000,
    "currency": "EUR",
    "customer_email": "test+sandbox@yours.com",
    "webhook_url": "https://xxxx.ngrok-free.app/webhooks/peptidepay"
  }'

# 3. Fire a SIGNED order.paid straight at your endpoint — no on-ramp, no
#    minimum, no KYC. (webhook_url_override also accepts localhost / ngrok
#    / loca.lt / trycloudflare.com if you'd rather not set it on the session.)
curl -X POST 'https://peptide-pay.com/api/v1/test/fire-webhook' \
  -H "Authorization: Bearer $PEPTIDEPAY_TEST_KEY" \
  -H 'Content-Type: application/json' \
  -d '{ "session_id": "PASTE_id_FROM_STEP_2" }'

# 4. The response echoes your receiver's HTTP status + the exact signature
#    header we sent, so you can line it up against your HMAC verification.

错误与速率限制

所有错误统一用 。状态码是标准 REST。

400
JSON 非法或字段缺失
请求体格式错误、amount 不是数字、wallet 不是 0x 地址、currency 不支持。
401
API 密钥无效或已吊销
Bearer token 无法解析到商家。到 /app/api-keys 轮换。
403
回调签名错误
内部错误 —— 我们的结算 IPN 打到 webhook 接收端但没带正确的会话级签名。正常情况下不会是面向商家的错误。
404
会话未找到
id 错了,或者会话已被清理(终态 > 90 天后)。
429
超出速率限制
init 每 IP 60 req/min,select 每 IP 30 req/min。返回 Retry-After 头。需更高等级联系支持。
502
上游不可用
结算网络暂时降级。用相同 Idempotency-Key 30 秒后重试。SLA 目标:99.5%+。

故障排查

后台显示会话 'paid' 但我的 webhook 从没触发
检查 webhook_url 能从公网 HTTPS 访问(在 LAN 外用 curl 测试)。如果没问题,用 GET /sessions/{id} 轮询确认状态 —— 后台 /app 会显示 webhook 投递统计(成功率、次数)。42 小时内 6 次尝试后死信;你随时能通过轮询重新同步。
HMAC 不匹配 —— 签名总是无效
99% 的情况:你 hash 的是已重新序列化的 body,而不是原始字节。框架在你处理函数之前自动解析 JSON;你需要原始 buffer。Next.js:任何 .json() 之前先 req.text()。Express:app.use('/webhooks', express.raw({ type: '*/*' }), …)。Rails:request.raw_post。还要检查你计算的是 `HMAC(whsec_secret, t + '.' + rawBody)` —— 而不是 `HMAC(whsec_secret, rawBody)`。时间戳前缀是必须的。
MoonPay 提示 '你所在国家不可用'
MoonPay 限制约 20 个国家(伊朗、朝鲜、古巴,完整列表在他们官网)。默认 provider 是 'gateway' —— 智能选路器会自动降级到 Revolut、Transak 或 Banxa,覆盖不同地区。如果你用 provider: 'moonpay' 指定了具体通道,去掉它让路由自己选。
'paid' 事件后我的钱包没收到 USDC
到 polygonscan.com/address/<your-wallet> 看 USDC(Polygon POS)转账。结算是 97% 到你,3% 到 Peptide-Pay —— 如果你没看到 97% 入账,可能是 init 调用里粘错了钱包。用 GET /sessions/{id} 再确认一下 —— txid 字段指向真实的链上转账。
客户被扣了两次
不应该发生。每个会话只有一个结算 addressIn;打到同一地址的第二笔付款在我们侧会变成独立会话,只有第一笔会计入你的订单。如果发生了,截图两笔 polygonscan txid + 会话 id 邮件给 hi@peptide-pay.com —— 我们从资金库退重复那笔。
出现 502 '支付基础设施暂时不可用'
我们的结算上游降级(< 0.5% 的请求)。用相同 Idempotency-Key 30 秒后重试 —— 钱包一旦生成成功,我们的缓存就返回原响应。关注 /status 查看实时事件。

准备好接入?

大多数商家从零到第一笔已付交易不到 30 分钟。