Create crypto charges directly from your system and grant the customer's access automatically when the payment confirms.
The typical flow of an integration (subscription, hosting, streaming):
1. Your system creates a charge via the API → POST https://api.stackfy.io/v1/invoices
2. You send the customer to payment → checkout link OR transparent QR
3. The customer pays in crypto
4. Stackfy calls your webhook → POST to your notification_url
5. You confirm the charge via the API → GET https://api.stackfy.io/v1/invoices/{id}
6. Status "complete" → you grant/renew the customer's access
Recurring: crypto has no automatic debit — on each cycle, your system repeats step 1.
| API address | https://api.stackfy.io/v1 |
| Your store_id | SEU_STORE_ID |
| Default currency | BRL |
Create a key in API and integration and send it in the header of every call:
Authorization: Bearer SUA_CHAVE
The key grants access to your account — keep it like a password. If it leaks, revoke it immediately from the panel.
POSThttps://api.stackfy.io/v1/invoices
| Field | Required | Description |
|---|---|---|
price | yes | Amount (string), e.g.: "49.90" |
currency | yes | Currency of the amount: BRL, USD, EUR… (or USDT for a direct 1:1 dollar price, only with pay_currency=USDT) |
store_id | yes | Your store: SEU_STORE_ID |
pay_currency | no | Omitted = multi-currency (the buyer picks the crypto — see below). To PIN the network: BTC, USDT (Tron/TRC-20) or USDT-MATIC (Polygon). Pinning USDT requires the store to have an address on that network. price stays in fiat. |
notification_url | no | Your webhook (receives the paid notification) |
redirect_url | no | Where to send the customer after paying |
metadata | no | Free-form object (e.g.: your order/user id) |
Just omit the pay_currency field: Stackfy creates an order and the buyer chooses between BTC, USDT-Tron and USDT-Polygon on the payment page (options depend on what the store has enabled). price stays in fiat; conversion happens at the live rate when the buyer chooses. The response returns pay_currency: "ANY" and the picker checkout_url.
curl -X POST https://api.stackfy.io/v1/invoices \
-H "Authorization: Bearer SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"price": "49.90",
"currency": "BRL",
"store_id": "SEU_STORE_ID"
}'
# no pay_currency = the buyer picks the crypto at checkout
curl -X POST https://api.stackfy.io/v1/invoices \
-H "Authorization: Bearer SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"price": "49.90",
"currency": "BRL",
"store_id": "SEU_STORE_ID",
"notification_url": "https://seusite.com/webhooks/stackfy",
"metadata": {"pedido": "1234", "usuario": "[email protected]"}
}'
const r = await fetch("https://api.stackfy.io/v1/invoices", {
method: "POST",
headers: {
"Authorization": "Bearer " + process.env.STACKFY_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
price: "49.90", currency: "BRL", store_id: "SEU_STORE_ID",
notification_url: "https://seusite.com/webhooks/stackfy",
metadata: { pedido: "1234" },
}),
});
const invoice = await r.json();
// invoice.id -> save it with your order
// redirect the customer to checkout (see below)
$ch = curl_init("https://api.stackfy.io/v1/invoices");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("STACKFY_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"price" => "49.90", "currency" => "BRL", "store_id" => "SEU_STORE_ID",
"notification_url" => "https://seusite.com/webhooks/stackfy",
]),
]);
$invoice = json_decode(curl_exec($ch), true);
The response is the created charge, with id, status ("pending") and the payment methods in payments (address, crypto amount and URI for QR).
Send the customer to Stackfy's ready-made checkout, using the charge id:
https://pay.stackfy.io/c/<invoice_id>
The customer sees the QR, pays and (if you set redirect_url) returns to your site.
Use the payments data from the response to build your own QR/address:
{
"id": "...",
"status": "pending",
"payments": [{
"payment_address": "bc1q...", // receiving address
"amount": "0.00071", // amount in crypto
"currency": "BTC",
"payment_url": "bitcoin:bc1q...?amount=0.00071" // becomes a QR
}]
}
When the charge is paid, Stackfy makes a signed POST to your server. You check the signature with your secret and that's it — no need to re-query anything.
In API and integration, save your server URL (receives the notification) and copy your signing secret (whsec_…). You don't need to set notification_url: every charge created by Stackfy (API https://api.stackfy.io/v1 or panel) already points to our relay on its own.
Stackfy receives the confirmation, checks the real status of the charge internally and only then resends it, already signed, to your server.
Header X-Stackfy-Signature: t=<timestamp>,v1=<hmac_sha256> and JSON body:
{
"id": "<invoice_id>",
"status": "complete",
"price": "49.90",
"currency": "BRL",
"paid_currency": "BTC",
"metadata": { "pedido": "1234" },
"store_id": "SEU_STORE_ID"
}
The signature is HMAC-SHA256 of the text "<timestamp>.<corpo_cru>" using your secret. Compare in constant time. Same scheme as Stripe.
const crypto = require("crypto");
// use the RAW body, not the already-parsed JSON:
app.post("/webhooks/stackfy", express.raw({type:"*/*"}), (req, res) => {
const raw = req.body.toString("utf8");
const [t, v1] = req.headers["x-stackfy-signature"].split(",")
.map(p => p.split("=")[1]);
const expected = crypto.createHmac("sha256", process.env.STACKFY_WHSEC)
.update(t + "." + raw).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
if (!ok) return res.status(401).end(); // forged -> reject
const ev = JSON.parse(raw);
if (ev.status === "complete") liberar(ev.metadata.pedido);
res.status(200).end(); // respond 200 quickly
});
$raw = file_get_contents("php://input");
parse_str(str_replace(",", "&", $_SERVER["HTTP_X_STACKFY_SIGNATURE"]), $sig);
$expected = hash_hmac("sha256", $sig["t"] . "." . $raw, getenv("STACKFY_WHSEC"));
if (!hash_equals($expected, $sig["v1"])) { http_response_code(401); exit; }
$ev = json_decode($raw, true);
if ($ev["status"] === "complete") liberar_acesso($ev["metadata"]["pedido"]);
http_response_code(200);
complete (or confirmed).If you'd rather not expose an endpoint, check the status whenever you want: GEThttps://api.stackfy.io/v1/invoices/<id> with your key. The truth is always in the API.
| status | Meaning | Release access? |
|---|---|---|
pending | Awaiting payment | No |
paid | Seen on the network, 0 confirmations | Not yet (may revert) |
confirmed | Confirmed on the network (≥1 conf) | Yes |
complete | Paid and settled | Yes |
expired | Time ran out without payment | No |
invalid | Invalid payment | No |
Integration questions? [email protected]