Wolf Pay

Developer API

Stablecoin payment API: integrate in an afternoon

Create a USDT or USDC payment with one REST call, send the customer to a hosted checkout, and fulfil orders from signed webhooks. Test mode with simulated payments.

Updated · Wolf Pay team

Wolf Pay is open in test mode: you can sign up, integrate and run test payments today. Live payments open once our custody partner goes live.

Key points

  • One POST creates a payment and returns a hosted checkout URL.
  • Signed webhooks (HMAC-SHA256) for paid, underpaid, overpaid, expired, refunds and payouts.
  • Idempotency keys on every create, decimal-string amounts, and consistent JSON errors.
  • Test keys simulate every outcome; live keys use the same code.
  • API keys can read payouts but never create them, so a leaked key can't move your money.

The flow

  1. Create a payment from your server.
  2. Redirect the customer to checkout_url, or show address and amount_due in your own UI.
  3. Fulfil on webhook: mark the order paid when payment.paid arrives.

1. Create a payment

curl https://wolf-pay-five.vercel.app/api/v1/payments \
  -H "Authorization: Bearer $WOLF_PAY_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order_1001" \
  -d '{"amount": "25.50", "currency": "USDT", "network": "tron", "order_id": "1001", "redirect_url": "https://yourstore.com/thanks"}'

Amounts are decimal strings so nothing is lost to floating-point rounding. Retrying with the same Idempotency-Key returns the original payment instead of creating a second one.

2. Send the customer to the checkout

The response includes checkout_url: a mobile-friendly page with the amount, QR code, address, network, countdown and live status. After a successful payment the customer is sent back to your redirect_url.

3. Verify the webhook and fulfil

Each event is signed in the Wolf-Signature header (t=…,v1=…), where v1 is the HMAC-SHA256 of <t>.<raw body> with your endpoint's secret.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWolfPay(rawBody: string, header: string, secret: string) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false;
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  return expected.length === parts.v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Answer with a 2xx within 10 seconds. Failed deliveries are retried 8 times over about 28 hours; use Wolf-Event-Id to skip duplicates.

Events

EventWhen
payment.paidPaid in full
payment.underpaid / payment.overpaidPaid less or more than due
payment.expiredExpired unpaid
refund.completedA refund reached the customer
payout.completedA payout reached your wallet or a recipient
invoice.paidAn invoice was paid in full

Test mode

wp_test_… keys create test payments. The checkout shows buttons to simulate paying in full, too little, too much, or letting the payment expire, and they trigger the same webhooks live payments do.

Also available

  • Refunds: POST /refunds sends money back to the address that paid.
  • Balance and payouts: read your balance and payouts; payouts are created in the dashboard only.
  • A Node.js SDK (@wolfpay/node), typed, with retries and idempotency built in. It's being published to npm; until then the REST API works from any language.

Full reference: API docs.

Frequently asked questions

Is there a test mode for the Wolf Pay API?

Yes. Test keys (wp_test_) create test payments, and the checkout has buttons to simulate full payment, underpayment, overpayment and expiry, with the same webhooks as live payments.

Can an API key send money out of my account?

No. API keys can create payments and refunds and read payouts, but payouts are created only in the dashboard by an owner or admin. A refund to any address other than the one that paid needs approval in the dashboard.

What are the API rate limits?

600 requests a minute per API key. Above that the API answers 429 with a Retry-After header.