API Documentation
Accept FIRO cryptocurrency payments on your website, app, or bot.
Overview
A self-hosted cryptocurrency payment gateway for Firo (FIRO). Full control over your payment processing on your own infrastructure.
How It Works
1. Your store calls POST /api/payments/create with the amount and order info.
2. The gateway generates a unique Firo address and returns a checkout_url pointing to your-gateway/invoice/{id}.
3. Redirect your buyer to the checkout URL. They send FIRO to the displayed address.
4. The gateway monitors the Firo blockchain. After 2 confirmations (~5 min) it fires your webhook.
5. Your webhook handler delivers the product/activates the service.
Authentication
All API requests require your API key in the X-API-Key header.
GET /api/payments/ X-API-Key: fgate_your_api_key_here
Get your API key from the Dashboard → API & Webhooks tab.
Error Codes
Create Payment
Creates a new payment invoice. Returns a unique Firo address and checkout URL.
Request Fields
Response
{
"payment_id": "193d7d11-5ade-4993-9ad9-...",
"checkout_url": "https://checkout.firogate.com/invoice/193d7d11...",
"receiving_address": "TRdhAfFoDhwYYLkDhchz...",
"amount_firo": 2.5,
"platform_fee_pct": 1.5,
"order_id": "ORD-1234",
"expires_at": "2025-01-15T14:30:00Z",
"required_confirmations": 2,
"status": "pending"
}
Error Codes
Get Payment Status
Check the status of a payment. Poll this every 10–30 seconds until status is confirmed.
Payment Status Values
List Payments
Returns your recent payments.
Query Parameters
pending / confirming / confirmed / expired / cancelledCancel Payment (Public)
Cancel a pending or confirming payment. Only works before the payment is confirmed.
Response
{
"cancelled": true,
"redirect_url": "https://yourstore.com/cancel",
"message": "Payment cancelled"
}
Webhook Events
The gateway sends a signed POST request to your webhook URL for various events. Each webhook includes a nonce and timestamp for replay protection.
Event Types
payment.confirmed payload
{
"event": "payment.confirmed",
"payment_id": "193d7d11-5ade-4993...",
"order_id": "ORD-1234",
"amount_firo": 2.5,
"amount_received": 2.5,
"platform_fee": 0.0375,
"merchant_net": 2.4625,
"txid": "a1b2c3d4e5f6...",
"confirmations": 2,
"customer_email": "buyer@example.com",
"confirmed_at": "2025-01-15T14:35:00Z",
"nonce": "7f8a9b0c1d2e3f4a...",
"timestamp": 1705312500
}
payment.cancelled payload
{
"event": "payment.cancelled",
"payment_id": "193d7d11-5ade-4993...",
"order_id": "ORD-1234",
"amount_firo": 2.5,
"status": "cancelled",
"cancelled_at": "2025-01-15T14:20:00Z",
"nonce": "9a8b7c6d5e4f3a2b...",
"timestamp": 1705311600
}
Python
import requests GATEWAY_URL = "https://api.firogate.com" API_KEY = "fgate_your_api_key" def create_payment(amount_firo, order_id, success_url): response = requests.post( f"{GATEWAY_URL}/api/payments/create", json={ "amount_firo": amount_firo, "order_id": order_id, "order_description": "Premium Subscription", "success_url": success_url, "cancel_url": "https://yourstore.com/cancel", "timeout_minutes": 20, }, headers={"X-API-Key": API_KEY}, ) response.raise_for_status() return response.json() # Usage payment = create_payment(2.5, "ORD-1234", "https://yourstore.com/success") print(f"Send buyer to: {payment['checkout_url']}") print(f"Payment ID: {payment['payment_id']}")
import time, requests def wait_for_payment(payment_id, timeout=1200): """Poll every 10s until confirmed or expired.""" start = time.time() while time.time() - start < timeout: r = requests.get( f"{GATEWAY_URL}/api/payments/{payment_id}", headers={"X-API-Key": API_KEY}, ) data = r.json() status = data["status"] if status == "confirmed": print(f"✅ Paid! TX: {data['txid']}") return data elif status == "expired": print("❌ Payment expired") return None print(f"Status: {status} ({data.get('confirmations',0)} confs)") time.sleep(10) return None
# Flask webhook handler with HMAC-SHA256 verification from flask import Flask, request, jsonify import hmac, hashlib, json, time app = Flask(__name__) WEBHOOK_SECRET = "your_webhook_secret_from_dashboard" @app.route("/webhook/firo", methods=["POST"]) def firo_webhook(): data = request.json sig = request.headers.get("X-FiroGate-Signature", "") # 1. Verify HMAC-SHA256 (sort_keys + compact separators) canonical = json.dumps(data, sort_keys=True, separators=(",", ":")).encode() expected = hmac.new(WEBHOOK_SECRET.encode(), canonical, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, sig): return jsonify({"error": "Invalid signature"}), 401 # 2. Verify timestamp (reject if older than 5 min) ts = data.get("timestamp", 0) if abs(time.time() - ts) > 300: return jsonify({"error": "Stale webhook"}), 400 # 3. Check nonce (store in DB/Redis to prevent replays) nonce = data.get("nonce") # if is_nonce_used(nonce): return jsonify({"error": "Replay"}), 409 # mark_nonce_used(nonce) # 4. Process event event = data.get("event") if event == "payment.confirmed": activate_order(data["order_id"], data["merchant_net"], data["txid"]) return jsonify({"ok": True})
JavaScript / Node.js
// Node.js — create payment const GATEWAY_URL = 'https://api.firogate.com'; const API_KEY = 'fgate_your_api_key'; async function createPayment(amountFiro, orderId, successUrl) { const res = await fetch(`${GATEWAY_URL}/api/payments/create`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY, }, body: JSON.stringify({ amount_firo: amountFiro, order_id: orderId, order_description: 'Premium Plan', success_url: successUrl, cancel_url: 'https://yourstore.com/cancel', timeout_minutes: 20, }), }); if (!res.ok) throw new Error(`Gateway error: ${res.status}`); return res.json(); } // Usage const payment = await createPayment(2.5, 'ORD-1234', 'https://yourstore.com/ok'); console.log(`Checkout: ${payment.checkout_url}`);
// Express.js webhook handler with HMAC-SHA256 verification const express = require('express'); const crypto = require('crypto'); const app = express(); const SECRET = 'your_webhook_secret'; app.post('/webhook/firo', express.json(), (req, res) => { const data = req.body; const sig = req.headers['x-firogate-signature'] || ''; // 1. Compute HMAC-SHA256 over sorted, compact JSON const keys = Object.keys(data).sort(); const sorted = {}; keys.forEach(k => sorted[k] = data[k]); const canonical = JSON.stringify(sorted); const expected = crypto .createHmac('sha256', SECRET) .update(canonical) .digest('hex'); if (!crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(sig) )) { return res.status(401).json({ error: 'Bad signature' }); } // 2. Verify timestamp (reject if older than 5 min) const ts = data.timestamp || 0; if (Math.abs(Date.now() / 1000 - ts) > 300) { return res.status(400).json({ error: 'Stale webhook' }); } // 3. Process event const { event, order_id, merchant_net, txid } = data; if (event === 'payment.confirmed') { activateOrder(order_id, merchant_net, txid); } res.json({ ok: true }); });
// Poll payment status until confirmed async function waitForPayment(paymentId, intervalMs = 10000) { return new Promise((resolve, reject) => { const iv = setInterval(async () => { const res = await fetch( `${GATEWAY_URL}/api/payments/${paymentId}`, { headers: { 'X-API-Key': API_KEY } } ); const data = await res.json(); if (data.status === 'confirmed') { clearInterval(iv); resolve(data); } else if (data.status === 'expired') { clearInterval(iv); reject(new Error('Payment expired')); } }, intervalMs); }); }
Java
import java.net.URI; import java.net.http.*; import java.net.http.HttpResponse.BodyHandlers; public class FiroGate { static final String GATEWAY = "https://api.firogate.com"; static final String API_KEY = "fgate_your_api_key"; public static String createPayment( double amount, String orderId, String successUrl ) throws Exception { String json = """ { "amount_firo": %s, "order_id": "%s", "success_url": "%s", "timeout_minutes": 20 }""".formatted(amount, orderId, successUrl); HttpRequest req = HttpRequest.newBuilder() .uri(URI.create(GATEWAY + "/api/payments/create")) .header("Content-Type", "application/json") .header("X-API-Key", API_KEY) .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, BodyHandlers.ofString()); System.out.println("Status: " + res.statusCode()); return res.body(); } }
import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.util.*; import com.google.gson.*; public class WebhookVerifier { static final String SECRET = "your_webhook_secret"; public static boolean verify(String jsonBody, String signature) throws Exception { // Parse + sort keys for canonical JSON JsonObject obj = JsonParser.parseString(jsonBody).getAsJsonObject(); TreeMap<String, JsonElement> sorted = new TreeMap<>(); obj.entrySet().forEach(e -> sorted.put(e.getKey(), e.getValue())); String canonical = new Gson().toJson(sorted); // HMAC-SHA256 Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(SECRET.getBytes(), "HmacSHA256")); byte[] hash = mac.doFinal(canonical.getBytes()); StringBuilder hex = new StringBuilder(); for (byte b : hash) hex.append(String.format("%02x", b)); return hex.toString().equals(signature); } } // Spring Boot controller: // String sig = request.getHeader("X-FiroGate-Signature"); // if (!WebhookVerifier.verify(body, sig)) return 401;
C# / .NET
using System.Net.Http; using System.Text; using System.Text.Json; var client = new HttpClient(); var gateway = "https://api.firogate.com"; var apiKey = "fgate_your_api_key"; async Task<string> CreatePayment( double amount, string orderId, string successUrl) { var payload = new { amount_firo = amount, order_id = orderId, order_description = "Product Purchase", success_url = successUrl, timeout_minutes = 20 }; var json = JsonSerializer.Serialize(payload); var req = new HttpRequestMessage(HttpMethod.Post, $"{gateway}/api/payments/create"); req.Headers.Add("X-API-Key", apiKey); req.Content = new StringContent(json, Encoding.UTF8, "application/json"); var res = await client.SendAsync(req); return await res.Content.ReadAsStringAsync(); } var result = await CreatePayment(2.5, "ORD-1234", "https://yourstore.com/ok"); Console.WriteLine(result);
using System.Security.Cryptography; using System.Text; using System.Text.Json; static bool VerifyWebhook( string jsonBody, string signature, string secret) { // Parse + sort keys for canonical JSON var dict = JsonSerializer.Deserialize <SortedDictionary<string, JsonElement>>(jsonBody); var canonical = JsonSerializer.Serialize(dict); // HMAC-SHA256 using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(canonical)); var expected = Convert.ToHexString(hash).ToLower(); return CryptographicOperations .FixedTimeEquals( Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signature)); } // ASP.NET Core: // var sig = Request.Headers["X-FiroGate-Signature"]; // if (!VerifyWebhook(body, sig, SECRET)) return Unauthorized();
PHP
<?php define('GATEWAY_URL', 'https://api.firogate.com'); define('API_KEY', 'fgate_your_api_key'); function createPayment($amountFiro, $orderId, $successUrl) { $ch = curl_init(GATEWAY_URL . '/api/payments/create'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'X-API-Key: ' . API_KEY, ], CURLOPT_POSTFIELDS => json_encode([ 'amount_firo' => $amountFiro, 'order_id' => $orderId, 'order_description' => 'Product Purchase', 'success_url' => $successUrl, 'timeout_minutes' => 20, ]), ]); $response = curl_exec($ch); curl_close($ch); return json_decode($response, true); } $payment = createPayment(2.5, 'ORD-1234', 'https://yourstore.com/ok'); header('Location: ' . $payment['checkout_url']); exit;
<?php // webhook.php — HMAC-SHA256 verification define('WEBHOOK_SECRET', 'your_webhook_secret'); $body = file_get_contents('php://input'); $data = json_decode($body, true); $sig = $_SERVER['HTTP_X_FIROGATE_SIGNATURE'] ?? ''; // Sort keys + compact JSON to match server-side signing ksort($data); $canonical = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $expected = hash_hmac('sha256', $canonical, WEBHOOK_SECRET); if (!hash_equals($expected, $sig)) { http_response_code(401); exit('Bad signature'); } // Verify timestamp $ts = $data['timestamp'] ?? 0; if (abs(time() - $ts) > 300) { http_response_code(400); exit('Stale webhook'); } if ($data['event'] === 'payment.confirmed') { $orderId = $data['order_id']; $netFiro = $data['merchant_net']; activateOrder($orderId, $netFiro); } echo json_encode(['ok' => true]);
Go
package main import ( "bytes" "encoding/json" "fmt" "net/http" ) const ( GatewayURL = "https://api.firogate.com" APIKey = "fgate_your_api_key" ) type PaymentRequest struct { AmountFiro float64 `json:"amount_firo"` OrderID string `json:"order_id"` OrderDescription string `json:"order_description"` SuccessURL string `json:"success_url"` TimeoutMinutes int `json:"timeout_minutes"` } type PaymentResponse struct { PaymentID string `json:"payment_id"` CheckoutURL string `json:"checkout_url"` AmountFiro float64 `json:"amount_firo"` Status string `json:"status"` } func CreatePayment(req PaymentRequest) (*PaymentResponse, error) { body, _ := json.Marshal(req) httpReq, _ := http.NewRequest("POST", GatewayURL+"/api/payments/create", bytes.NewBuffer(body)) httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("X-API-Key", APIKey) resp, err := http.DefaultClient.Do(httpReq) if err != nil { return nil, err } defer resp.Body.Close() var result PaymentResponse json.NewDecoder(resp.Body).Decode(&result) return &result, nil } func main() { p, _ := CreatePayment(PaymentRequest{ AmountFiro: 2.5, OrderID: "ORD-1234", SuccessURL: "https://yourstore.com/ok", TimeoutMinutes: 20, }) fmt.Println("Checkout:", p.CheckoutURL) }
package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "math" "net/http" "time" ) const WebhookSecret = "your_webhook_secret" func webhookHandler(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) sig := r.Header.Get("X-FiroGate-Signature") // Parse + re-marshal (Go sorts map keys by default) var data map[string]interface{} json.Unmarshal(body, &data) canonical, _ := json.Marshal(data) // Verify HMAC-SHA256 mac := hmac.New(sha256.New, []byte(WebhookSecret)) mac.Write(canonical) expected := hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(expected), []byte(sig)) { http.Error(w, "Unauthorized", 401) return } // Verify timestamp ts, _ := data["timestamp"].(float64) if math.Abs(float64(time.Now().Unix())-ts) > 300 { http.Error(w, "Stale", 400) return } if data["event"] == "payment.confirmed" { fmt.Println("Payment confirmed:", data["order_id"]) } w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"ok":true}`)) }
Ruby
require 'net/http' require 'json' GATEWAY_URL = 'https://api.firogate.com' API_KEY = 'fgate_your_api_key' def create_payment(amount_firo, order_id, success_url) uri = URI("#{GATEWAY_URL}/api/payments/create") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == 'https' req = Net::HTTP::Post.new(uri) req['Content-Type'] = 'application/json' req['X-API-Key'] = API_KEY req.body = { amount_firo: amount_firo, order_id: order_id, order_description: 'Digital Product', success_url: success_url, timeout_minutes: 20 }.to_json response = http.request(req) JSON.parse(response.body) end payment = create_payment(2.5, 'ORD-1234', 'https://yourstore.com/ok') puts "Checkout: #{payment['checkout_url']}"
require 'sinatra' require 'openssl' require 'json' WEBHOOK_SECRET = 'your_webhook_secret' post '/webhook/firo' do body = request.body.read data = JSON.parse(body) sig = request.env['HTTP_X_FIROGATE_SIGNATURE'] # Sort keys + compact JSON to match server signing canonical = JSON.generate(data.sort.to_h) expected = OpenSSL::HMAC.hexdigest('SHA256', WEBHOOK_SECRET, canonical) halt 401 unless Rack::Utils.secure_compare(expected, sig.to_s) # Verify timestamp ts = data['timestamp'].to_i halt 400 if (Time.now.to_i - ts).abs > 300 if data['event'] == 'payment.confirmed' activate_order(data['order_id'], data['merchant_net']) end {ok: true}.to_json end
Rust
use reqwest; use serde_json::json; const GATEWAY: &str = "https://api.firogate.com"; const API_KEY: &str = "fgate_your_api_key"; async fn create_payment( amount: f64, order_id: &str, success_url: &str, ) -> reqwest::Result<serde_json::Value> { let client = reqwest::Client::new(); let body = json!({ "amount_firo": amount, "order_id": order_id, "order_description": "Product Purchase", "success_url": success_url, "timeout_minutes": 20 }); let res = client.post(format!("{}/api/payments/create", GATEWAY)) .header("X-API-Key", API_KEY) .json(&body) .send().await?; res.json().await } // Usage: // let p = create_payment(2.5, "ORD-1234", "https://yourstore.com/ok").await?; // println!("Checkout: {}", p["checkout_url"]);
use hmac::{Hmac, Mac}; use sha2::Sha256; use serde_json::Value; use std::collections::BTreeMap; type HmacSha256 = Hmac<Sha256>; fn verify_webhook( json_body: &str, signature: &str, secret: &str, ) -> bool { // Parse + BTreeMap auto-sorts keys let data: BTreeMap<String, Value> = serde_json::from_str(json_body).unwrap(); let canonical = serde_json::to_string(&data).unwrap(); // HMAC-SHA256 let mut mac = HmacSha256::new_from_slice( secret.as_bytes()).unwrap(); mac.update(canonical.as_bytes()); let result = mac.finalize(); let expected = hex::encode(result.into_bytes()); // Constant-time compare expected == signature } // Actix-web / Axum handler: // let sig = req.headers().get("X-FiroGate-Signature"); // if !verify_webhook(&body, sig, SECRET) { return 401; }
cURL
curl -X POST https://api.firogate.com/api/payments/create \ -H "X-API-Key: fgate_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "amount_firo": 2.5, "order_id": "ORD-1234", "order_description": "Premium Plan", "success_url": "https://yourstore.com/ok", "timeout_minutes": 20 }'
curl https://api.firogate.com/api/payments/PAYMENT_ID \ -H "X-API-Key: fgate_your_api_key"
curl "https://api.firogate.com/api/payments/?limit=10&status=confirmed" \ -H "X-API-Key: fgate_your_api_key"
Telegram Bot Integration
Complete example: a Telegram bot that creates FIRO payment requests and notifies you when confirmed.
# pip install python-telegram-bot[job-queue] requests import asyncio, requests, time from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import Application, CommandHandler, ContextTypes GATEWAY_URL = "https://api.firogate.com" API_KEY = "fgate_your_api_key" BOT_TOKEN = "your_telegram_bot_token" ADMIN_CHAT = 123456789 # your Telegram user ID def gw_create(amount, order_id): r = requests.post(f"{GATEWAY_URL}/api/payments/create", json={"amount_firo": amount, "order_id": order_id, "timeout_minutes": 20}, headers={"X-API-Key": API_KEY}, timeout=15) return r.json() if r.ok else None async def pay_cmd(update: Update, ctx: ContextTypes.DEFAULT_TYPE): if not ctx.args: await update.message.reply_text("Usage: /pay 2.5"); return amount = float(ctx.args[0]) order_id = f"TG-{int(time.time())}" r = gw_create(amount, order_id) if not r: await update.message.reply_text("❌ Gateway error"); return kb = [[InlineKeyboardButton("Go to Checkout →", url=r["checkout_url"])]] await update.message.reply_text( f"💳 Send *{amount:.4f} FIRO*\n" f"Order: `{order_id}`\n" f"Expires: 20 minutes", parse_mode="Markdown", reply_markup=InlineKeyboardMarkup(kb), ) if __name__ == "__main__": app = Application.builder().token(BOT_TOKEN).build() app.add_handler(CommandHandler("pay", pay_cmd)) app.run_polling()
Setting Up Webhooks
Configure your webhook URL in Dashboard → API & Webhooks.
Requirements for your webhook endpoint
{"ok": true}X-FiroGate-Signature (HMAC-SHA256). Always verify before processing.
Webhook Headers
POST /your-webhook-endpoint HTTP/1.1 Content-Type: application/json X-FiroGate-Event: payment.confirmed X-FiroGate-Signature: a1b2c3d4e5f6... <-- HMAC-SHA256 X-FiroGate-Nonce: 7f8a9b0c... <-- unique per request X-FiroGate-Timestamp: 1705312500 <-- unix timestamp User-Agent: FiroGate/1.0
Signature Algorithm
The signature is computed as HMAC-SHA256(json_body, webhook_secret) where the JSON body is serialized with sorted keys and compact separators (, and : with no spaces).
# Python equivalent of how the server signs: import json, hmac, hashlib body = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() sig = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
Replay Protection
Every webhook includes a nonce (random hex string) and timestamp (unix seconds). Your handler should:
1. Reject stale webhooks — if abs(now - timestamp) > 300 (5 minutes)
2. Reject replays — store seen nonces in Redis/DB and reject duplicates
Verify Webhook Signature
The signature is HMAC-SHA256(sorted_compact_json, webhook_secret). Keys are sorted alphabetically, separators are , and : with no spaces.
import hmac, hashlib, json, time def verify_webhook(payload: dict, signature: str, secret: str) -> bool: # 1. Recompute signature (sorted keys, compact separators) canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() expected = hmac.new(secret.encode(), canonical, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, signature): return False # 2. Check timestamp freshness ts = payload.get("timestamp", 0) if abs(time.time() - ts) > 300: return False return True # Django example def webhook_view(request): data = json.loads(request.body) sig = request.headers.get('X-FiroGate-Signature', '') if not verify_webhook(data, sig, WEBHOOK_SECRET): return HttpResponse(status=401) # process event...
const crypto = require('crypto'); function verifyWebhook(payload, signature, secret) { // Sort keys + compact JSON to match server signing const keys = Object.keys(payload).sort(); const sorted = {}; keys.forEach(k => sorted[k] = payload[k]); const canonical = JSON.stringify(sorted); const expected = crypto .createHmac('sha256', secret) .update(canonical) .digest('hex'); try { return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signature) ); } catch { return false; } } // Usage in Express: const sig = req.headers['x-firogate-signature']; if (!verifyWebhook(req.body, sig, SECRET)) { ... }
function verifyWebhook($data, $signature, $secret): bool { // Sort keys + compact JSON ksort($data); $canonical = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); $expected = hash_hmac('sha256', $canonical, $secret); return hash_equals($expected, $signature); } // Usage $body = file_get_contents('php://input'); $data = json_decode($body, true); $sig = $_SERVER['HTTP_X_FIROGATE_SIGNATURE'] ?? ''; if (!verifyWebhook($data, $sig, WEBHOOK_SECRET)) { http_response_code(401); exit; }
import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "sort" ) func verifyWebhook(data map[string]interface{}, signature, secret string) bool { // json.Marshal sorts keys by default in Go canonical, _ := json.Marshal(data) mac := hmac.New(sha256.New, []byte(secret)) mac.Write(canonical) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signature)) } // Usage: sig = r.Header.Get("X-FiroGate-Signature")
Tor / Onion Access
FiroGate supports a .onion hidden service. One onion address serves the API, checkout, and dashboard — no subdomain routing needed.
https://api.firogate.com with your .onion address. Every endpoint, header, and response is identical.Environment Variables
http://yourxxx.onion9050)Tor Hidden Service (torrc)
Point HiddenServicePort to your backend. The generated hostname file is your ONION_URL value.
HiddenServiceDir /var/lib/tor/firogate/ HiddenServicePort 80 127.0.0.1:8000
Making API Calls Over Tor
# --socks5-hostname routes DNS resolution through Tor curl --socks5-hostname 127.0.0.1:9050 \ -X POST http://yourxxx.onion/api/payments/create \ -H "X-API-Key: fgate_your_api_key" \ -H "Content-Type: application/json" \ -d '{"amount_firo":2.5,"order_id":"ORD-1234","timeout_minutes":20}'
# pip install requests[socks] import requests ONION_URL = "http://yourxxx.onion" API_KEY = "fgate_your_api_key" proxies = { "http": "socks5h://127.0.0.1:9050", "https": "socks5h://127.0.0.1:9050", } r = requests.post( f"{ONION_URL}/api/payments/create", json={ "amount_firo": 2.5, "order_id": "ORD-1234", "timeout_minutes": 20, }, headers={"X-API-Key": API_KEY}, proxies=proxies, timeout=30, ) payment = r.json() print(f"Checkout: {payment['checkout_url']}")
// npm install socks-proxy-agent node-fetch const { SocksProxyAgent } = require('socks-proxy-agent'); const fetch = require('node-fetch'); const ONION_URL = 'http://yourxxx.onion'; const API_KEY = 'fgate_your_api_key'; const agent = new SocksProxyAgent('socks5h://127.0.0.1:9050'); const res = await fetch(`${ONION_URL}/api/payments/create`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY, }, body: JSON.stringify({ amount_firo: 2.5, order_id: 'ORD-1234', timeout_minutes: 20, }), agent, }); const payment = await res.json(); console.log(`Checkout: ${payment.checkout_url}`);
socks5h:// (not socks5://) to ensure DNS resolution also goes through Tor and your real IP is never exposed.
AI Agent Integration Prompt
Copy the message below and provide it to your AI coding agent to generate a complete payment integration.
## Payment Gateway Integration — AI Agent Instructions Your task is to integrate a FIRO cryptocurrency payment gateway into my website or backend service. Tell him what is your service and what to add or in which place tell him exactly after payment confirmed what to do and how to make the users use your service or your product don't mis anything we just confirming the paymets then fire to your webhok Mr. or Ms user Follow all instructions carefully. ── API Base URL ── https://api.firogate.com ── Authentication ── All API requests require the header: X-API-Key: <API_KEY> IMPORTANT: - The API key shown above is a placeholder. - NEVER hardcode the API key in source code. - ALWAYS load it from a secure backend environment variable. Example: export FIROGATE_API_KEY="your_real_api_key" ── Create Payment ── Send a POST request to create a payment: POST /api/payments/create Headers: Content-Type: application/json X-API-Key: (read from environment variable) Body: { "amount_firo": 2.5, "order_id": "ORD-1234", "order_description": "Product Name", "success_url": "https://yoursite.com/success", "cancel_url": "https://yoursite.com/cancel", "timeout_minutes": 20 } Response: - The response will include checkout_url - Redirect the buyer to this URL to complete the payment ── Webhook Handler ── After payment confirmation, FiroGate sends a POST request to your webhook endpoint. Headers: X-FiroGate-Event: payment.confirmed X-FiroGate-Signature: <hmac_sha256_hex> X-FiroGate-Nonce: <unique_per_request> X-FiroGate-Timestamp: <unix_seconds> ── Webhook Secret ── Store your webhook secret securely: export FIROGATE_WEBHOOK_SECRET="your_real_secret" NEVER expose this secret in frontend code or public repositories. ── Verify Webhook Signature ── To validate incoming webhooks: 1. Parse the JSON payload 2. Sort keys alphabetically 3. Serialize using compact JSON (no spaces) 4. Compute: HMAC-SHA256(sorted_json, webhook_secret) 5. Compare with X-FiroGate-Signature 6. Reject if timestamp is older than 5 minutes 7. Reject if nonce was already used (prevent replay attacks) Python example: import os, json, hmac, hashlib, time secret = os.environ["FIROGATE_WEBHOOK_SECRET"] def verify_webhook(payload, signature): canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() expected = hmac.new(secret.encode(), canonical, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, signature): return False if abs(time.time() - payload.get("timestamp", 0)) > 300: return False return True ── Security Rules (Critical) ── - NEVER hardcode API keys or secrets - NEVER expose secrets to the frontend - NEVER send secrets to any AI tool or third-party service - ALWAYS validate webhook signatures before processing - ALWAYS enforce timestamp validation (max 5 minutes) - ALWAYS track nonces to prevent replay attacks - ALWAYS use HTTPS ── Expected Integration Flow ── 1. User clicks "Pay with FIRO" 2. Backend creates payment using API key (from env) 3. Backend returns checkout_url 4. User is redirected to checkout page 5. User completes payment 6. Webhook is sent to backend 7. Backend verifies signature and timestamp 8. Order is marked as paid 9. User is redirected to success page ── Final Notes ── - All sensitive operations must happen on the backend only - The frontend should only handle UI and redirects - Treat all webhook data as untrusted until verified