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.
Od czego zacząć
Peptide-Pay obsługuje cztery tryby integracji. Wybierz ten, który pasuje do twoich ograniczeń; API pod spodem jest to samo.
Drop-in przycisk, bez rejestracji. Podaj adres walletu USDC w body requestu. Zero stanu backendu; publiczne, widoczne w DevTools.
Server-to-server. Ukryj wallet za Bearer sk_live_. Konfiguruj branding, webhooki, mass payouty z dashboardu.
Gotowy plugin. Wgraj ZIP, wklej swój API key + webhook secret, zamówienia auto-kompletują się na płatność. HPOS-ready, WC 7.0+.
Custom App + Ręczna Metoda Płatności. ~30 min instalacji na istniejącym sklepie. Oznaczamy zamówienia jako opłacone przez Shopify Admin API.
Quickstart (5 min)
Trzy kroki: stwórz sesję, przekieruj klienta, obsłuż webhook. Sample poniżej to produkcyjny checkout route w Node.js.
// 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:
| Schemat | Jak | Kiedy |
|---|---|---|
| Bearer token | Authorization: Bearer sk_live_… | Po stronie serwera. Trzyma wallet prywatny. |
| Wallet w body | { "wallet": "0x…", … } | Strony statyczne / widgety bez backendu. |
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.
#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.
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| amount_cents | integer | wymagane | Kwota w najmniejszej jednostce waluty (centy). Zakres 100 – 10 000 000. |
| currency | string | wymagane | Kod ISO 4217. Wspierane: EUR, USD, GBP, CAD, AUD, CHF. |
| wallet | string | one-of | Wallet USDC na Polygonie (0x + 40 hex). Wymagany chyba że autoryzowany przez Bearer key. |
| customer_email | string | opcjonalne | Pokazywany w UI checkoutu i przekazywany do on-rampy dla reużycia KYC. |
| success_url | url | opcjonalne | Redirect po udanej płatności. Tylko http/https. |
| cancel_url | url | opcjonalne | Redirect jak klient porzuci checkout. |
| webhook_url | url | opcjonalne | POST target dla eventów order.paid. Nadpisuje default z dashboardu. |
| provider | string | opcjonalne | Default 'gateway' (smart picker — rekomendowany). Albo przypnij konkretny on-ramp id z GET /providers (np. moonpay, revolut, banxa, transak). |
| product_name | string | opcjonalne | Etykieta pokazywana na stronie checkoutu (max 80 znaków). |
| metadata | object | opcjonalne | Do 10 par string key/value, echo w webhooku. Zarezerwowany klucz: order_id. |
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| id | string | opcjonalne | ID sesji, zaczyna się od cs_. |
| url | string | opcjonalne | URL hosted checkoutu, gdzie przekierować klienta. |
| status | string | opcjonalne | Zawsze "pending" przy tworzeniu. |
| amount | integer | opcjonalne | Echo amount_cents. |
| currency | string | opcjonalne | Echo currency. |
| provider | string | opcjonalne | Echo provider (default 'gateway'). |
| expires_at | string | opcjonalne | ISO 8601 wygaśnięcia (24h od utworzenia). |
| tracking_number | string | opcjonalne | Adres 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);#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.
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| id | string | opcjonalne | ID sesji. |
| status | string | opcjonalne | pending | paid | expired | failed. |
| amount | integer | opcjonalne | Oryginalna kwota w centach. |
| currency | string | opcjonalne | Oryginalna waluta. |
| paid_at | string|null | opcjonalne | ISO 8601 kiedy zakończyło się rozliczenie on-chain. |
| paid_provider | string|null | opcjonalne | Który provider faktycznie przetworzył płatność (może się różnić od wybranego). |
| txid | string|null | opcjonalne | Txid rozliczenia Polygon. Link z polygonscan.com/tx/{txid}. |
| expires_at | string | opcjonalne | ISO 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');
}#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.
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
| providers[].id | string | opcjonalne | Klucz providera (przekazywalny jako `provider` w /checkout/init). |
| providers[].provider_name | string | opcjonalne | Human label do dropdowna. |
| providers[].status | string | opcjonalne | 'active' (zawsze filtrowane do active na tym endpoincie). |
| providers[].minimum_currency | string | opcjonalne | Kod ISO minimum. |
| providers[].minimum_amount | number | opcjonalne | Najniż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
| Event | Kiedy |
|---|---|
| order.paid | Rozliczenie 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.
// 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');
}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 .
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
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);
});peptide-payPełne typy, helper do webhooków, automatyczne retry.
dowolny językJeden POST, jeden GET. Bez biblioteki.
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ści | Ty płacisz | Klient płaci |
|---|---|---|
| Karta / Apple Pay / Google Pay | 3% | ~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.
400401403404429502Troubleshooting
- 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.