Payment gateway integration
Accept crypto on your site with Bit2Fiat SCI. Create invoices from your server, redirect buyers to a hosted pay page, then settle into the merchant wallet and notify your backend via IPN.
Quick start
- Create a Bit2Fiat account and open Settings → Payment gateway.
- Enable SCI and copy your
api_keyandapi_secret(secret is shown once). - From your server,
POST https://bit2fiat.com/sciwith amount, coin, and order details. - Redirect the buyer to
payment_urlfrom the response. - Listen for IPN webhooks (and/or poll status) to mark the order paid.
Authentication
All create-payment requests must authenticate the merchant. Prefer HMAC signatures in production so the secret never leaves your server in query strings.
Option A — API secret (server-to-server)
Send credentials in the JSON/form body or headers:
| Field / header | Description |
|---|---|
api_key | Public merchant key (also X-Api-Key or Basic auth username). |
api_secret | Private secret (also X-Api-Secret or Basic auth password). |
Authorization: Basic … | base64(api_key:api_secret) |
Option B — HMAC signature
Build a message and sign with your secret using HMAC-SHA256 (hex):
api_key|coin|amount|order_id|timestamp|nonce
| Field | Rules |
|---|---|
timestamp | Unix seconds; must be within ±300s of server time. |
nonce | Random unique string per request. |
signature | hash_hmac('sha256', message, api_secret) as hex. |
order_id | Use empty string in the message if you omit order_id. |
$message = implode('|', [$apiKey, $coin, $amount, $orderId, $timestamp, $nonce]);
$signature = hash_hmac('sha256', $message, $apiSecret);
Create a payment
CSRF is not required for this endpoint (server-to-server). Send JSON with
Content-Type: application/json and
Accept: application/json for a JSON response.
Without JSON headers, a successful create redirects to the pay page.
Request fields
| Field | Required | Description |
|---|---|---|
api_key | Yes* | Merchant API key (*or via header / Basic auth). |
api_secret / signature | Yes | One of secret or HMAC signature. |
coin | Yes | Asset code, e.g. USDT_TRC20, BTC. |
amount | Yes | Human decimal amount, e.g. 25.00. |
order_id | No | Your reference (unique per merchant while active). |
success_url | No | Buyer redirect after paid (we append payment_id, order_id, status). |
cancel_url | No | Buyer cancel / expired return URL. |
ipn_url | No | Your webhook URL for payment notifications. |
buyer_email | No | Optional buyer contact. |
json | No | Set 1 to force JSON response. |
cURL example
curl -X POST 'https://bit2fiat.com/sci' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"api_key": "b2f_YOUR_KEY",
"api_secret": "YOUR_SECRET",
"coin": "USDT_TRC20",
"amount": "25.00",
"order_id": "ORD-1042",
"success_url": "https://yoursite.com/paid",
"cancel_url": "https://yoursite.com/cancel",
"ipn_url": "https://yoursite.com/webhooks/bit2fiat"
}'
Success response
{
"ok": true,
"payment_id": "uuid",
"payment_url": "https://bit2fiat.com/pay/uuid",
"address": "T…",
"coin": "USDT_TRC20",
"amount": "25",
"order_id": "ORD-1042",
"expires_at": "2026-08-06T14:00:00+00:00",
"status": "pending"
}
payment_url. Do not show raw deposit addresses from your own UI unless you also poll status yourself.
Checkout page & status
Hosted pay page (QR, address, countdown, partial payment progress).
JSON status for polling (the pay page polls this every few seconds).
{
"payment_id": "uuid",
"order_id": "ORD-1042",
"status": "pending",
"status_label": "Awaiting payment",
"coin": "USDT_TRC20",
"address": "T…",
"amount_expected": "25",
"amount_received": "0",
"amount_remaining": "25",
"progress_pct": 0,
"success_url": null,
"expires_at": "…"
}
IPN (merchant webhooks)
When a payment status changes in a way that settles (paid / overpaid) or reports underpaid, Bit2Fiat
POSTs form fields to your ipn_url.
Respond with HTTP 200 (body containing ok is preferred). Failed deliveries are retried with backoff.
IPN fields
| Field | Description |
|---|---|
payment_id | Bit2Fiat payment UUID |
order_id | Your order reference |
status | e.g. paid, overpaid, underpaid |
coin | Coin code |
amount_expected | Invoice amount (human) |
amount_received | Amount buyer sent (human) |
amount_remaining | Still due (human) |
fee_amount | Platform product fee if configured |
tx_hash | Latest on-chain transaction |
confirmations | Confirmations observed |
address | Deposit address |
paid_at / credited_at | ISO timestamps when set |
signature | HMAC of other fields |
Verify IPN signature
Sort all fields except signature by key ascending, join as
key=value with &, then:
ksort($payload);
unset($payload['signature']);
$pairs = [];
foreach ($payload as $k => $v) {
if (is_array($v)) continue;
$pairs[] = $k.'='.$v;
}
$expected = hash_hmac('sha256', implode('&', $pairs), $apiSecret);
hash_equals($expected, $receivedSignature);
Payment statuses
| Status | Meaning |
|---|---|
pending | Invoice created; waiting for chain payment. |
confirming | Transaction seen; waiting for confirmations. |
underpaid | Partial payment received; remaining amount still due (same address). |
paid | Fully paid; merchant wallet credited for invoice amount. |
overpaid | Buyer sent more than invoice; still settles as success (invoice amount credited). |
expired | Payment window ended without full settlement. |
cancelled | Cancelled (reserved). |
Supported coins
Pass the exact code in the coin field. Currently active:
Errors
JSON error shape:
{ "ok": false, "error": "Human readable message" }
| HTTP | Typical cause |
|---|---|
401 | Missing/invalid api_key, secret, or signature. |
422 | Validation failed, unsupported coin, duplicate finished order_id, etc. |
500 | Unexpected server/provider error (e.g. address generation). |
Security checklist
- Keep
api_secretonly on your server — never in browser JS or mobile apps. - Prefer HMAC signatures for create requests in production.
- Always verify IPN
signaturebefore fulfilling orders. - Use HTTPS for
success_url,cancel_url, andipn_url. - Make fulfillment idempotent: the same payment_id may notify more than once.