Documentation
Payment API
Create checkout links with your API key.
Programmatically create checkout links on api.firewallet.sh, share them with customers on pay.firewallet.sh, and receive signed webhooks when payments are created or updated.
Base URL
https://api.firewallet.sh
All endpoints live under /api/v1/.
Authentication
Every request must include your account API key:
Authorization: Bearer fw_xxxxxxxx
Generate or rotate the key in Settings → API. Keys start with fw_. Store them securely; the full key is only shown once when rotated.
Invalid or missing keys return 401 with code UNAUTHORIZED.
Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/payments |
List your API-created payments |
POST |
/api/v1/payments |
Create a payment checkout |
GET |
/api/v1/payments/:id |
Get one payment by numeric id |
PATCH |
/api/v1/payments/:id |
Update amount, methods, metadata, or webhook |
Successful responses use the standard Firewallet JSON envelope (success: true, data: { ... }).
Create a payment
POST /api/v1/payments
{
"amount": 42.5,
"devis": "eur",
"whitelabel": false,
"paymentMethods": ["tron"],
"data": {
"orderId": "ORD-1001",
"customerEmail": "buyer@example.com"
},
"webhook": "https://your-app.example/webhooks/firewallet"
}
Request fields
| Field | Required | Description |
|---|---|---|
amount |
yes | Fiat amount (positive number, rounded to 2 decimals) |
devis |
yes | eur or usd |
whitelabel |
yes | true = customer picks a coin on checkout; false = exactly one paymentMethods entry |
paymentMethods |
yes | Asset ids with an active receive address on your account (see below) |
data |
no | String key-value metadata echoed in API responses and webhooks |
webhook |
no | HTTPS URL for payment lifecycle events |
webhookSecret |
no | Your signing secret (16–64 chars). If webhook is set and this is omitted, Firewallet generates wh_... and returns it once in the response |
successRedirectUrl |
no | HTTPS URL for non-whitelabel checkouts. After on-chain confirmation, the payer is redirected here from the pay page |
Payment method ids
Use the asset id from your wallet (same ids as the dashboard):
bitcoin(BTC)litecoin(LTC)ethereum(ETH)usdt_erc20(USDT on Ethereum)tron(TRX)usdt_trc20(USDT on Tron)solana(SOL)monero(XMR)
Each method must map to an available API wallet slot for that asset. Otherwise the API returns 400 with INVALID_PAYMENT.
Confirmed API payments follow the same automatic swap rules as dashboard deposits when automation is enabled under Payment settings (toMonero). Funds on API wallet slots (BTC, LTC, and other supported inbound coins) are converted to Monero once they pass your EUR threshold. Monero API deposits are already XMR and skip the inbound swap; outbound routing still applies from your shield settings. Open payments are never swapped until they confirm or expire.
Whitelabel vs single-method
whitelabel: false—paymentMethodsmust contain exactly one id. Checkout shows that coin only.whitelabel: true—paymentMethodsmay list several ids. The customer chooses one on the pay page.
Example response
{
"success": true,
"data": {
"payment": {
"id": 12,
"slug": "pl_xYz123",
"amount": 42.5,
"devis": "eur",
"whitelabel": false,
"paymentMethods": ["tron"],
"data": { "orderId": "ORD-1001" },
"webhook": "https://your-app.example/webhooks/firewallet",
"checkoutUrl": "https://pay.firewallet.sh/pl_xYz123",
"status": "pending",
"timeLeftSeconds": 3600,
"detectionTimeLeftSeconds": 1800,
"paidCoinId": null,
"paidAmount": null,
"paidTxId": null,
"confirmations": null,
"successRedirectUrl": "https://your-app.example/payment/success",
"createdAt": 1710000000000,
"updatedAt": 1710000000000
},
"webhookSecret": "wh_a1b2c3d4e5f6..."
}
}
`
Share `checkoutUrl` with the payer. API-created links use the same neutral public slug style as regular payment links.
## Update a payment
`PATCH /api/v1/payments/:id`
Send only fields you want to change. The same validation rules apply (for example, non-whitelabel payments still require exactly one payment method when `paymentMethods` is sent).
Updating a payment triggers a `payment.updated` webhook when a webhook URL is configured.
## List and get
- `GET /api/v1/payments` — returns `{ payments: [...] }` for the authenticated account.
- `GET /api/v1/payments/:id` — returns `{ payment }` or `404 NOT_FOUND`.
## Webhooks
When `webhook` is set on create or update, Firewallet sends a `POST` with JSON body:
```json
{
"event": "payment.created",
"payment": { "...": "same shape as API payment object" },
"emittedAt": 1710000000000
}
Events:
| Event | When |
|---|---|
payment.created |
Payment request created |
payment.updated |
Payment fields updated via API |
payment.detected |
Incoming funds detected (mempool or unconfirmed) |
payment.confirmed |
Payment confirmed on-chain |
payment.expired |
No payment detected within 30 minutes, or not confirmed before the 60-minute window ends |
The backend watches allocated API wallet addresses for up to 60 minutes. The payer UI waits up to 30 minutes for first detection, then shows a confirmation state until the payment is confirmed or expires.
Headers
| Header | Meaning |
|---|---|
X-Firewallet-Event |
Event name |
X-Firewallet-Signature |
Hex HMAC-SHA256 of the raw body using your webhookSecret |
X-Firewallet-Delivery |
Unique delivery id (for logging) |
Verify the signature
Compute HMAC-SHA256 of the raw request body with your webhookSecret and compare it to X-Firewallet-Signature (use a constant-time compare).
Node.js example:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyFirewalletWebhook(rawBody, headers, secret) {
const signature = headers['x-firewallet-signature'];
if (!signature) return false;
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(signature);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
Use the raw request body bytes (before JSON parsing) when verifying.
Webhook delivery is best-effort: failed HTTP calls do not roll back the payment operation.
Errors
| HTTP | Code | Typical cause |
|---|---|---|
401 |
UNAUTHORIZED |
Missing or invalid API key |
400 |
INVALID_PAYMENT |
Validation failed (amount, methods, missing address, etc.) |
404 |
NOT_FOUND |
Unknown payment id |
422 |
— | Request body failed schema validation |
Dashboard vs automation API
Dashboard (firewallet.sh) |
Automation API (api.firewallet.sh) |
|
|---|---|---|
| Auth | Session cookie | Authorization: Bearer API key |
| Creates | Payment links in the UI | Same underlying payment links, via JSON |
| Checkout | pay.firewallet.sh/{slug} |
Same pay site; API returns checkoutUrl |
The internal session API under firewallet.sh/api/account/payment-links is separate from this external automation API.
Quick test with curl
curl -sS -X POST 'https://api.firewallet.sh/api/v1/payments' \
-H 'Authorization: Bearer fw_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"amount": 10,
"devis": "eur",
"whitelabel": false,
"paymentMethods": ["tron"]
}'