Referencja devowska

API Peptide-Pay

REST API do obsługi kart i krypto, rozliczenie w USDC na wallecie, który kontrolujesz. Jeden POST tworzy checkout. Jeden webhook mówi, że zapłacono. Wysłane pod 30 minut.

REST · JSONBearer authWebhooki HMAC-SHA256Idempotency-KeyCORS-enabled

Od czego zacząć

Quickstart (5 min)

Trzy kroki: stwórz sesję, przekieruj klienta, obsłuż webhook. Sample poniżej to produkcyjny checkout route w 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);

To cały happy path. Klient ląduje na hosted checkoucie na , wybiera kartę albo krypto, a twój webhook odpala się w ciągu 30s od płatności.

Autentykacja

Dwa schematy, zależnie od trybu integracji:

SchematJakKiedy
Bearer tokenAuthorization: Bearer sk_live_…Po stronie serwera. Trzyma wallet prywatny.
Wallet w body{ "wallet": "0x…", … }Strony statyczne / widgety bez backendu.
Uważaj
API keys niosą pełną tożsamość konta merchanta — traktuj je jak hasła. Nigdy ich nie commituj, nigdy nie wysyłaj do przeglądarki, rotuj z /app/api-keys jak wycieknie.
Tip
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.

Referencja API

Base URL: . Wszystkie endpointy gadają JSON-em, zwracają jeden obiekt na sukces, a obiekt na 4xx/5xx.

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

#Stwórz sesję checkoutu

Generuje URL hosted checkoutu. Klient go otwiera, płaci kartą albo krypto, Peptide-Pay rozlicza na twój wallet w USDC, webhook się odpala.

Body requestu
PoleTypWymaganeOpis
amount_centsintegerwymaganeKwota w najmniejszej jednostce waluty (centy). Zakres 100 – 10 000 000.
currencystringwymaganeKod ISO 4217. Wspierane: EUR, USD, GBP, CAD, AUD, CHF.
walletstringone-ofWallet USDC na Polygonie (0x + 40 hex). Wymagany chyba że autoryzowany przez Bearer key.
customer_emailstringopcjonalnePokazywany w UI checkoutu i przekazywany do on-rampy dla reużycia KYC.
success_urlurlopcjonalneRedirect po udanej płatności. Tylko http/https.
cancel_urlurlopcjonalneRedirect jak klient porzuci checkout.
webhook_urlurlopcjonalnePOST target dla eventów order.paid. Nadpisuje default z dashboardu.
providerstringopcjonalneDefault 'gateway' (smart picker — rekomendowany). Albo przypnij konkretny on-ramp id z GET /providers (np. moonpay, revolut, banxa, transak).
product_namestringopcjonalneEtykieta pokazywana na stronie checkoutu (max 80 znaków).
metadataobjectopcjonalneDo 10 par string key/value, echo w webhooku. Zarezerwowany klucz: order_id.
Odpowiedź (200 OK)
PoleTypWymaganeOpis
idstringopcjonalneID sesji, zaczyna się od cs_.
urlstringopcjonalneURL hosted checkoutu, gdzie przekierować klienta.
statusstringopcjonalneZawsze "pending" przy tworzeniu.
amountintegeropcjonalneEcho amount_cents.
currencystringopcjonalneEcho currency.
providerstringopcjonalneEcho provider (default 'gateway').
expires_atstringopcjonalneISO 8601 wygaśnięcia (24h od utworzenia).
tracking_numberstringopcjonalneAdres rozliczenia Polygon — pasuje do address_in w payloadzie webhooka, używalne z /track dla live monitoringu.

Przykłady

// 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);
Tip
Podaj , żeby retry były safe. Odtwarzamy cache'owaną odpowiedź przez 24h na ten sam key zamiast generować nową sesję (zapobiega double-charge na niestabilnych sieciach).
GEThttps://peptide-pay.com/api/v1/sessions/{id}

#Pobierz sesję (polling)

Używaj jako fallback dla webhooka albo do hydratacji strony sukcesu po redirectcie. Server-side re-checkuje naszą warstwę rozliczenia na każdym callu — tanio (<200ms), więc polling co 3-5s jest ok.

Odpowiedź
PoleTypWymaganeOpis
idstringopcjonalneID sesji.
statusstringopcjonalnepending | paid | expired | failed.
amountintegeropcjonalneOryginalna kwota w centach.
currencystringopcjonalneOryginalna waluta.
paid_atstring|nullopcjonalneISO 8601 kiedy zakończyło się rozliczenie on-chain.
paid_providerstring|nullopcjonalneKtóry provider faktycznie przetworzył płatność (może się różnić od wybranego).
txidstring|nullopcjonalneTxid rozliczenia Polygon. Link z polygonscan.com/tx/{txid}.
expires_atstringopcjonalneISO 8601 wygaśnięcia.
// 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

#Live matryca providerów

Listuje on-rampy, które aktualnie przyjmują ruch, z minimalnymi kwotami per provider. Cache'owane 5 min na edge — pooluj raz przy bootcie appa, nie per request.

Odpowiedź
PoleTypWymaganeOpis
providers[].idstringopcjonalneKlucz providera (przekazywalny jako `provider` w /checkout/init).
providers[].provider_namestringopcjonalneHuman label do dropdowna.
providers[].statusstringopcjonalne'active' (zawsze filtrowane do active na tym endpoincie).
providers[].minimum_currencystringopcjonalneKod ISO minimum.
providers[].minimum_amountnumberopcjonalneNajniższa kwota, którą provider przyjmuje (w jednostkach 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.

Webhooki

Gdy sesja dociera do stanu terminalnego, POST-ujemy podpisany event JSON na , który skonfigurowałeś (per-sesja albo w dashboardzie). Zawsze parsuj body requestu do weryfikacji podpisu — re-serializacja JSON zmienia kolejność kluczy i psuje 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
}

Typy eventów

EventKiedy
order.paidRozliczenie on-chain potwierdzone. + + gwarantowane obecne. Oznacz zamówienie jako opłacone.

Tylko jest dostarczane dziś — sesje expired i failed są obserwowalne przez (status idzie na po TTL 24h; terminalne failure pokazują ). Możemy dodać push eventy dla nich w przyszłym release.

Weryfikacja podpisu

Merchanci z kontem signup dostają sekret , a każde dostarczenie niesie header w formacie . Oblicz i constant-time porównaj z . Odrzuć cokolwiek starszego niż 5 minut.

Tip
Flow wallet-only (bez signup, bez ) dostarczają webhook bez podpisu — powinieneś mimo to zwalidować, że pasuje do sesji, którą stworzyłeś. Dla podpisanych dostarczeń zarejestruj się na /signup po swój sekret.
// 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');
}
Uważaj
Zawsze używaj constant-time compare dla swojego języka: (Node), (Python), (PHP), (Ruby). Zwykły wycieka HMAC bajt po bajcie do timing attackera.

Polityka retry

Retry-ujemy nie-2xx odpowiedzi (i timeouty > 5s) na exponential backoff. Sześć prób w sumie przez ~42 godziny:

  • Próba 1 — natychmiast przy potwierdzeniu.
  • Próba 2 — +5 minut.
  • Próba 3 — +15 minut.
  • Próba 4 — +1 godzina.
  • Próba 5 — +4 godziny.
  • Próba 6 — +12 godzin, potem +24 godziny (ostatnia).

Po 6 nieudanych próbach event idzie do dead-letter. Zawsze możesz re-requestować aktualny stan przez .

Tip
Zrób swój handler idempotentnym. Dedupuj po — retry może re-fire paid event, który już przetworzyłeś.

Typowe problemy

Widzę 'invalid signature' na każdym dostarczeniu
Twój framework sparsował body jako JSON zanim go zhaszowałeś. Czytaj RAW bajty (Express: express.raw({type:'*/*'}); Next.js: req.text(); Laravel: request()->getContent(); Rails: request.raw_post). Nigdy nie re-serializuj przed haszowaniem.
IP whitelist — z jakich IP wysyłacie?
Dostarczenia aktualnie lecą z Vercel Edge (dynamiczne IP). Nie publikujemy statycznego zakresu. Jak MUSISZ whitelistować, użyj header podpisu jako auth gate i akceptuj dowolne source IP — HMAC to prawdziwy check tożsamości.
HTTPS wymagane?
Tak. Odmawiamy POST-owania na endpointy http:// (confusable deputy / plaintext replay risk). Darmowy URL https ngrok działa ok do lokalnego testowania.
Mój endpoint jest wolny — mogę wydłużyć 5s timeout?
Nie. Odpowiedz 2xx od razu, potem przetwarzaj asynchronicznie (job queue, setImmediate, goroutine). Długie blokujące handlery zawsze kończą się timeoutem.

SDK

API jest na tyle małe, że jest zupełnie ok — ale Node SDK daje ci typy, automatyczne retry i helper , który ogarnia weryfikację podpisu za ciebie.

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 / TypeScriptstable
peptide-pay

Pełne typy, helper do webhooków, automatyczne retry.

Bezpośrednio fetch()zawsze działa
dowolny język

Jeden POST, jeden GET. Bez biblioteki.

Tip
SDK dla Python, PHP, Ruby i Go są na roadmapie. Dopóki nie wyjdą, raw sample // powyżej to kanoniczna referencja — nie popsujemy ich.

Opłaty

Flat — pełna prowizja Peptide-Pay. Bez subskrypcji, bez miesięcznej, bez opłat za chargebacki. Opłaty on-rampy kartowej (~4.5% pobierane przez upstreamowy card processor) są pass-through — klient je płaci, nigdy nie dotykają twojego payoutu.

Metoda płatnościTy płaciszKlient płaci
Karta / Apple Pay / Google Pay3%~4.5% (on-ramp, pass-through)
Krypto direct (USDC → USDC)3%tylko gas (~$0.01 na Polygonie)

Pełne rozbicie z policzonymi przykładami na /fees.

Testowanie

Każde nowe konto merchant dostaje za darmo — pełna opłata 3% wraca na twój wallet w ciągu 24h. Użyj ich, żeby przećwiczyć pełen flow end-to-end (prawdziwa karta, prawdziwe USDC, prawdziwy webhook) zanim wjedziesz live.

  • Tryb sandbox jest automatyczny: pierwsze 3 opłacone sesje per merchant są oznaczane i kwalifikują się do auto-refundu.
  • Karta dev MoonPay: , dowolna data ważności w przyszłości, dowolny CVV, ZIP 10001.
  • Testowanie webhooków lokalnie: wystaw localhost przez ngrok, wklej URL do pola per sesja.

Pełna lokalna pętla (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.

Błędy i rate limity

Wszystkie błędy dzielą kształt . Kody statusu są standardowe REST.

400
Nieprawidłowy JSON albo brak pola
Body zepsute, amount nie-numeryczne, wallet nie jest adresem 0x, currency niewspierana.
401
Nieprawidłowy albo odwołany API key
Bearer token nie rozwiązuje się do merchanta. Rotuj na /app/api-keys.
403
Zły podpis callbacka
Wewnętrzne — nasz settlement IPN trafił w odbiorcę webhooka bez poprawnego podpisu per-sesja. Nie jest to błąd widoczny dla merchanta w normalnej operacji.
404
Sesja nie znaleziona
Zły id, albo sesja została wypruniona (> 90d po stanie terminalnym).
429
Przekroczono rate limit
60 req/min/IP na init, 30 req/min/IP na select. Header Retry-After dołączony. Kontakt z supportem po wyższe tieriy.
502
Upstream niedostępny
Sieć rozliczenia tymczasowo zdegradowana. Retry za 30s z tym samym Idempotency-Key. Cel SLA: 99.5%+.

Troubleshooting

Sesja jest 'paid' w dashboardzie, ale mój webhook nigdy się nie odpalił
Sprawdź, że webhook_url jest osiągalny przez publiczny HTTPS (curl-nij go spoza twojego LAN). Jak jest ok, pooluj GET /sessions/{id} dla potwierdzenia statusu — dashboard /app pokazuje staty dostarczenia webhooka (success rate, liczniki). Sześć prób przez 42h przed dead-letter; zawsze możesz re-syncować przez polling.
HMAC mismatch — podpis zawsze nieprawidłowy
99% czasu: haszujesz re-serializowane body zamiast raw bajtów. Frameworki auto-parsują JSON zanim twój handler się odpali; potrzebujesz raw buffera. Next.js: req.text() przed jakimkolwiek .json(). Express: app.use('/webhooks', express.raw({ type: '*/*' }), …). Rails: request.raw_post. Sprawdź też, że liczysz `HMAC(whsec_secret, t + '.' + rawBody)` — NIE tylko `HMAC(whsec_secret, rawBody)`. Prefiks timestampa jest WYMAGANY.
MoonPay mówi 'service unavailable in your country'
MoonPay restrykcje ~20 krajów (Iran, Korea Północna, Kuba, pełna lista na ich stronie). Domyślny provider to 'gateway' — smart picker auto-fallbackuje do Revolut, Transak albo Banxa, które pokrywają inne geografie. Jak przypiąłeś konkretnego providera z provider: 'moonpay', odepnij i daj routerowi wybrać.
Mój wallet nie dostał USDC po evencie 'paid'
Sprawdź polygonscan.com/address/<twój-wallet> pod transfery USDC (Polygon POS). Rozliczenie ląduje 97% do ciebie i 3% do Peptide-Pay - jak nie widzisz przychodzących 97%, mogłeś wkleić zły wallet do wywołania init. Potwierdź ponownie przez GET /sessions/{id} - pole txid wskazuje prawdziwy on-chain transfer.
Klient został obciążony dwa razy
Nie powinno się zdarzyć. Każda sesja ma jeden addressIn rozliczenia; drugie przelew na ten sam adres staje się osobną sesją po naszej stronie i kredytujemy tylko pierwszy do twojego zamówienia. Jak się zdarzy, screenshot dwóch txid z polygonscan + id sesji i mail na hi@peptide-pay.com - zwrócimy duplikat z naszej treasury.
Dostaję 502 'Payment infrastructure temporarily unavailable'
Nasz upstream rozliczenia jest zdegradowany (< 0.5% requestów). Retry za 30s z tym samym Idempotency-Key - nasz cache zwraca oryginalną odpowiedź gdy tylko wallet się wygeneruje poprawnie. Trackuj /status dla live incydentów.

Gotowy na integrację?

Większość merchantów idzie od zera do pierwszej opłaconej transakcji pod 30 minut.