开发者参考

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

测试

每个新商家账户免费获得 —— 完整的 3% 手续费 24 小时内退到你钱包。用它端到端演练完整流程(真实卡、真实 USDC、真实 webhook),上线前先跑一遍。

  • 沙箱模式 自动启用:每个商家的前 3 笔已付会话会标记 并自动退款。
  • MoonPay 开发测试卡:,过期日期随便填,CVV 随便填,ZIP 10001。
  • 本地 webhook 测试:用 ngrok 暴露 localhost,把 URL 粘到会话的 字段。

完整本地回路(ngrok)

# 1. Expose your local webhook endpoint
ngrok http 3000

# 2. Copy the https://xxxx.ngrok-free.app URL and paste it into
#    Dashboard → Webhooks → Endpoint URL, OR send it inline:
curl -X POST 'https://peptide-pay.com/api/v1/checkout/init' \
  -H "Authorization: Bearer $PEPTIDEPAY_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "amount_cents": 100,
    "currency": "EUR",
    "customer_email": "test+sandbox@yours.com",
    "success_url": "https://yours.com/success",
    "cancel_url":  "https://yours.com/cart",
    "webhook_url": "https://xxxx.ngrok-free.app/webhooks/peptidepay"
  }'

# 3. Open the returned `url`, hit MoonPay's dev test card
#    4242 4242 4242 4242 (any future exp, any CVV).
# 4. Your local endpoint receives the signed POST within ~30s of payment.

错误与速率限制

所有错误统一用 。状态码是标准 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 分钟。