개발자 레퍼런스

Peptide-Pay API

카드 및 암호화폐 결제를 처리하고, 여러분이 제어하는 USDC 월렛으로 정산하는 REST API. 한 번의 POST로 체크아웃을 생성합니다. 한 개의 웹훅이 결제 완료를 알려줍니다. 30분 이내에 출시하세요.

REST · JSONBearer 인증HMAC-SHA256 웹훅Idempotency-KeyCORS 활성화

시작하기

퀵스타트 (5분)

세 단계: 세션 생성, 고객 리다이렉트, 웹훅 처리. 아래 샘플은 프로덕션 준비 완료된 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);

그게 해피 패스의 전부입니다. 고객은 의 호스티드 체크아웃에 도착해 카드 또는 암호화폐를 선택하고, 여러분의 웹훅이 결제 후 30초 이내에 발사됩니다.

인증

연동 모드에 따라 두 가지 스킴:

스킴방법사용 시점
Bearer 토큰Authorization: Bearer sk_live_…서버 사이드. 월렛을 비공개로 유지합니다.
본문의 월렛{ "wallet": "0x…", … }백엔드가 없는 정적 사이트 / 위젯.
주의
API 키는 가맹점 계정의 전체 신원을 담습니다 — 비밀번호처럼 다루세요. 절대 커밋하지 말고, 브라우저에 노출하지 말고, 유출 시 /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로 정산하며, 웹훅이 발사됩니다.

요청 본문
필드타입필수설명
amount_centsinteger필수통화의 최소 단위 금액 (센트). 범위 100 – 10 000 000.
currencystring필수ISO 4217 코드. 지원: EUR, USD, GBP, CAD, AUD, CHF.
walletstringone-ofPolygon의 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개의 문자열 키/값 쌍, 웹훅에서 그대로 반환됩니다. 예약된 키: 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 정산 주소 — 웹훅 페이로드의 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시간 동안 캐시된 응답을 재생하므로 새 세션을 생성하지 않습니다 (불안정한 네트워크에서 이중 청구 방지).
GEThttps://peptide-pay.com/api/v1/sessions/{id}

#세션 조회 (폴링)

웹훅 폴백으로 사용하거나, 리다이렉트 후 성공 페이지를 하이드레이션할 때 사용하세요. 서버는 매 호출마다 정산 레이어를 재확인합니다 — 저렴하므로 (<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선택프로바이더 키 (/checkout/init에서 `provider`로 전달 가능).
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.

웹훅

세션이 종료 상태에 도달하면 설정된 (세션별 또는 대시보드)로 서명된 JSON 이벤트를 POST합니다. 시그니처 검증을 위해 항상 요청 본문을 파싱하세요 — JSON을 재직렬화하면 키 순서가 바뀌어 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온체인 정산 확인됨. + + 이 반드시 존재합니다. 주문을 결제 완료로 표시하세요.

현재 만 전송됩니다 — 만료되거나 실패한 세션은 를 통해 관찰 가능합니다 (24h TTL 이후 status는 가 되고, 종료 실패는 로 표시됨). 향후 릴리스에서 이들을 위한 푸시 이벤트를 추가할 수 있습니다.

시그니처 검증

가입 계정이 있는 가맹점은 시크릿을 받으며, 모든 전달에는 형식의 헤더가 포함됩니다. 을 계산하고 상수-시간 방식으로 과 비교하세요. 5분보다 오래된 것은 거부하세요.

월렛 전용 플로우(가입 없음, 없음)는 웹훅을 서명 없이 전송합니다 — 여전히 이 생성한 세션과 일치하는지 검증해야 합니다. 서명된 전달을 받으려면 /signup에서 가입해 시크릿을 받으세요.
// 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회 실패 후 이벤트는 데드레터됩니다. 를 통해 언제든지 현재 상태를 재요청할 수 있습니다.

핸들러를 idempotent하게 만드세요. 로 중복 제거 — 재시도가 이미 처리한 paid 이벤트를 재발송할 수 있습니다.

일반적인 이슈

모든 전달에서 'invalid signature'가 표시됩니다
프레임워크가 해시 전에 본문을 JSON으로 파싱했습니다. RAW 바이트를 읽으세요 (Express: express.raw({type:'*/*'}); Next.js: req.text(); Laravel: request()->getContent(); Rails: request.raw_post). 해싱 전에 재직렬화하지 마세요.
IP 화이트리스트 — 어떤 IP에서 보내시나요?
전달은 현재 Vercel Edge(동적 IP)에서 발송됩니다. 정적 범위를 공개하지 않습니다. 반드시 화이트리스트가 필요하다면, 시그니처 헤더를 인증 게이트로 사용하고 모든 소스 IP를 허용하세요 — HMAC이 진짜 신원 확인입니다.
HTTPS 필수?
네. http:// 엔드포인트로는 POST를 거부합니다 (혼동된 대리인 / 평문 재전송 위험). ngrok의 무료 https URL은 로컬 테스트에서 잘 작동합니다.
엔드포인트가 느립니다 — 5초 타임아웃을 연장할 수 있나요?
아니요. 즉시 2xx를 응답한 다음 비동기로 처리하세요 (작업 큐, setImmediate, 고루틴). 긴 블로킹 핸들러는 항상 타임아웃됩니다.

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

전체 타입, 웹훅 헬퍼, 자동 재시도.

직접 fetch()항상 작동
모든 언어

한 번의 POST, 한 번의 GET. 라이브러리 불필요.

Python, PHP, Ruby, Go SDK는 로드맵에 있습니다. 출시 전까지 위의 raw // 샘플이 표준 레퍼런스입니다 — 저희는 이를 깨뜨리지 않습니다.

수수료

고정 — Peptide-Pay의 전체 커미션. 구독 없음, 월 정액 없음, 차지백 수수료 없음. 카드 온램프 수수료(업스트림 카드 프로세서가 부과하는 ~4.5%)는 패스스루입니다 — 고객이 지불하며, 여러분의 페이아웃에는 절대 영향을 주지 않습니다.

결제 수단여러분이 지불고객이 지불
카드 / Apple Pay / Google Pay3%~4.5% (온램프, 패스스루)
암호화폐 직접 (USDC → USDC)3%가스비만 (~$0.01 on Polygon)

상세 계산 예시: /fees.

테스트

모든 신규 가맹점 계정은 을 무료로 받습니다 — 3% 수수료 전액이 24시간 이내에 월렛으로 환불됩니다. 라이브 전환 전에 전체 플로우를 엔드투엔드(실제 카드, 실제 USDC, 실제 웹훅)로 리허설할 때 사용하세요.

  • 샌드박스 모드는 자동입니다: 가맹점당 첫 3개의 유료 세션이 로 표시되어 자동 환불 대상이 됩니다.
  • MoonPay 개발용 카드: , 아무 미래 유효기간, 아무 CVV, ZIP 10001.
  • 로컬 웹훅 테스트: 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 또는 누락된 필드
본문 형식 오류, 금액이 숫자가 아님, 월렛이 0x 주소가 아님, 통화 미지원.
401
유효하지 않거나 취소된 API 키
Bearer 토큰이 가맹점으로 확인되지 않습니다. /app/api-keys에서 회전시키세요.
403
잘못된 콜백 시그니처
내부 — 저희 정산 IPN이 올바른 세션별 시그니처 없이 웹훅 수신기에 도달했습니다. 일반 운영에서 가맹점에 표시되는 에러가 아닙니다.
404
세션을 찾을 수 없음
잘못된 id 또는 세션이 프루닝됨 (종료 상태 이후 > 90일).
429
레이트 리밋 초과
init에서 60 req/min/IP, select에서 30 req/min/IP. Retry-After 헤더 포함. 더 높은 티어는 지원팀에 문의하세요.
502
업스트림 사용 불가
정산 네트워크가 일시적으로 저하됨. 동일한 Idempotency-Key로 30초 후 재시도하세요. SLA 목표: 99.5%+.

트러블슈팅

대시보드에서 세션이 'paid'이지만 웹훅이 발사되지 않았어요
webhook_url이 공개 HTTPS로 접근 가능한지 확인하세요 (LAN 외부에서 curl해보세요). 올바르다면 GET /sessions/{id}로 폴링해 상태를 확인하세요 — 대시보드 /app이 웹훅 전달 통계(성공률, 카운트)를 보여줍니다. 데드레터 전까지 42시간에 걸쳐 6회 시도; 폴링으로 언제든지 재동기화할 수 있습니다.
HMAC 불일치 — 시그니처가 항상 무효입니다
99%의 경우: 재직렬화된 본문을 해싱하고 있습니다. raw 바이트를 해시해야 합니다. 프레임워크가 핸들러 실행 전에 JSON을 자동 파싱합니다; raw 버퍼가 필요합니다. 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개국(이란, 북한, 쿠바, 자세한 목록은 그들의 사이트)을 제한합니다. 기본 프로바이더는 '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 'Payment infrastructure temporarily unavailable'가 표시됩니다
저희 정산 업스트림이 저하됨 (요청의 < 0.5%). 동일한 Idempotency-Key로 30초 후 재시도하세요 - 월렛이 성공적으로 생성되는 즉시 캐시가 원래 응답을 반환합니다. 라이브 인시던트는 /status에서 추적하세요.

연동 준비되셨나요?

대부분의 가맹점은 가입부터 첫 유료 거래까지 30분 이내에 도달합니다.