Renovax API
REST API for merchant operations (payment links, refunds) and reseller operations (user provisioning, credit sales). All responses are JSON. Authentication uses Bearer tokens.
Overview
The API exposes two independent surfaces, each with its own token type:
- Merchant API — scoped to a single merchant. Use for creating payment links, fetching invoices, and issuing refunds. Authenticated with a merchant token.
- Reseller API — scoped to a user with the
reseller,distributor, oradminrole. Use for provisioning end-user accounts and selling credits. Authenticated with a user token.
Base URL: https://payments.renovax.net/api
Authentication
Generate tokens from the dashboard:
- Merchant token: Merchant → Edit → API Tokens. Shown once; store it securely.
- User token: Profile → API Tokens. Shown once; store it securely.
Send the token as a Bearer header on every request:
Authorization: Bearer <your_token>
Accept: application/json
Content-Type: application/json
Merchant tokens can only call /merchant/* endpoints. User tokens can only call
/reseller/* and legacy /invoices endpoints.
Merchant API
All endpoints below require a merchant token.
GET /merchant/me
Returns the authenticated merchant.
curl https://payments.renovax.net/api/merchant/me \
-H "Authorization: Bearer $TOKEN"
Response 200:
{
"id": 7,
"name": "Acme Store",
"default_currency": "USD",
"public_uuid": "9c1f...-...-...",
"customer_pays_fee": false,
"fx_markup_percent": "1.000",
"is_active": true
}
GET /merchant/invoices
Paginated invoice list. Query params: status, per_page (max 100).
curl "https://payments.renovax.net/api/merchant/invoices?status=confirmed&per_page=20" \
-H "Authorization: Bearer $TOKEN"
Response 200:
{
"data": [
{
"id": "01J...",
"merchant_id": 7,
"amount": "49.90",
"currency": "USD",
"status": "confirmed",
"client_remote_id": "order-42",
"pay_url": "https://.../pay/01J...",
"expires_at": "2026-04-21T23:59:59+00:00",
"paid_at": "2026-04-21T23:12:03+00:00",
"created_at": "2026-04-21T23:00:00+00:00"
}
],
"meta": { "current_page": 1, "last_page": 3, "per_page": 20, "total": 48 }
}
POST /merchant/invoices
Creates a payment link. Returns the invoice with a pay_url to share.
curl -X POST https://payments.renovax.net/api/merchant/invoices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount": 49.90,
"currency": "USD",
"client_remote_id": "order-42",
"success_url": "https://yoursite.com/thanks",
"cancel_url": "https://yoursite.com/cart",
"metadata": { "source": "checkout" },
"expires_in_minutes": 60
}'
Response 201:
{
"id": "01J...",
"merchant_id": 7,
"amount": "49.90",
"currency": "USD",
"status": "pending",
"client_remote_id": "order-42",
"success_url": "https://yoursite.com/thanks",
"cancel_url": "https://yoursite.com/cart",
"metadata": { "source": "checkout" },
"expires_at": "2026-04-21T23:59:59+00:00",
"paid_at": null,
"pay_url": "https://.../pay/01J...",
"created_at": "2026-04-21T23:00:00+00:00"
}
If an invoice with the same client_remote_id exists, it is returned
with status 200 (idempotent).
GET /merchant/invoices/{id}
Fetches a single invoice by id.
curl https://payments.renovax.net/api/merchant/invoices/01J... \
-H "Authorization: Bearer $TOKEN"
Response 200:
{
"id": "01J...",
"merchant_id": 7,
"amount": "49.90",
"currency": "USD",
"status": "confirmed",
"client_remote_id": "order-42",
"success_url": "https://yoursite.com/thanks",
"cancel_url": "https://yoursite.com/cart",
"metadata": { "source": "checkout" },
"expires_at": "2026-04-21T23:59:59+00:00",
"paid_at": "2026-04-21T23:12:03+00:00",
"pay_url": "https://.../pay/01J...",
"created_at": "2026-04-21T23:00:00+00:00"
}
POST /merchant/invoices/{id}/refund
Refunds a confirmed payment. Leave amount empty for a full refund.
Supported drivers: stripe, paypal.
curl -X POST https://payments.renovax.net/api/merchant/invoices/01J.../refund \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "amount": 10.00 }'
Response 200:
{
"status": "success",
"provider_refund_id": "re_1N...",
"amount": "10.00",
"currency": "USD",
"message": "Refunded successfully"
}
Reseller API
All endpoints below require a user token for an account with role
reseller, distributor, or admin.
GET /reseller/me
Returns the authenticated reseller (balance + fee percent).
curl https://payments.renovax.net/api/reseller/me \
-H "Authorization: Bearer $TOKEN"
Response 200:
{
"id": 4,
"username": "mario",
"email": "[email protected]",
"role": "distributor",
"credits": 398.5,
"fee_percent": 15
}
GET /reseller/users
Lists users created by you. Supports per_page.
curl "https://payments.renovax.net/api/reseller/users?per_page=20" \
-H "Authorization: Bearer $TOKEN"
Response 200:
{
"data": [
{ "id": 123, "username": "alice42", "email": "[email protected]", "name": "Alice", "role": "user", "credits": 98.0, "created_at": "2026-04-21T..." }
],
"meta": { "current_page": 1, "last_page": 1, "total": 1 }
}
POST /reseller/users
Creates a new user. If password is omitted, a random one is generated and
returned once in the response.
curl -X POST https://payments.renovax.net/api/reseller/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "alice42",
"email": "[email protected]",
"name": "Alice"
}'
Response 201:
{
"user": { "id": 123, "username": "alice42", "email": "[email protected]", "role": "user", "credits": 0 },
"password": "Xk3p9..." // only present when generated on the server
}
POST /reseller/credits/sell
Transfers credits from your balance to another user. A percent fee is deducted according to your role. Recipient accepts username or email.
curl -X POST https://payments.renovax.net/api/reseller/credits/sell \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"recipient": "alice42",
"amount": 100,
"note": "initial load"
}'
Response 200:
{
"gross": 100,
"fee": 2.0,
"net": 98.0,
"balance_after": 398.5,
"recipient": { "id": 123, "username": "alice42", "credits": 98.0 }
}
GET /reseller/credits/history
Returns your transfer ledger (transfer_out, transfer_in,
transfer_fee).
curl "https://payments.renovax.net/api/reseller/credits/history?per_page=30" \
-H "Authorization: Bearer $TOKEN"
Response 200:
{
"data": [
{ "id": 501, "reason": "transfer_out", "delta": -100.0, "balance_after": 398.5, "note": "initial load", "created_at": "2026-04-21T..." }
],
"meta": { "current_page": 1, "last_page": 1, "total": 1 }
}
Webhooks (outbound to your server)
When an invoice changes state, RENOVAX Payments POSTs a JSON body to the
webhook_url configured in your merchant settings. Every request is
signed with your merchant webhook_secret so you can verify it came
from us.
Headers
Every delivery carries these headers:
Content-Type: application/json
X-Renovax-Event-Id: evt_8f3a0c1e4b2d47a9b5e6f1c2d3e4a5b6
X-Renovax-Event-Type: invoice.paid
X-Renovax-Signature: sha256=3f2c9a1b8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a
Event types
| Event | When it fires |
|---|---|
invoice.paid | Payment confirmed on-chain (crypto) or by provider webhook (Stripe/PayPal). Status confirmed. |
invoice.partial | Payment received but amount is below expected (within configured tolerance). Status confirmed. |
invoice.overpaid | Payment received but amount exceeds expected (within configured tolerance). Status confirmed. |
invoice.failed | Invoice auto-cancelled by anti-fraud (Tor/VPN/datacenter/Scamalytics). Status failed. |
invoice.expired | TTL reached without payment. Status expired. |
Payload example (invoice.paid)
{
"event_type": "invoice.paid",
"invoice_id": "019dbc6b-33a9-7145-82c7-a53cfe533dc8",
"merchant_id": 42,
"status": "confirmed",
"invoice_amount": "100.00",
"invoice_currency": "USD",
"amount_received": "100.00000000",
"currency": "USDT",
"fee": "2.50000000",
"amount_net": "97.50000000",
"amount_received_fiat":"1800.00",
"amount_net_fiat": "1755.00",
"fx_rate_used": "18.000000",
"fx_pair": "USD/MXN",
"customer_paid_fee": false,
"driver": "crypto:USDT:ethereum",
"tx_hash": "0x7a8b9c…",
"network": "ethereum",
"confirmations": 12,
"provider_id": null,
"paid_at": "2026-04-23T15:29:58Z",
"confirmed_at": "2026-04-23T15:29:58Z",
"metadata": { "user_id": 4321 }
}
tx_hash / network / confirmations are only set for crypto payments;
provider_id is set for Stripe/PayPal captures.
amount_received_fiat, amount_net_fiat, fx_rate_used, fx_pair
are only present when the merchant's currency differs from the payment currency.
amount_net depends on your customer_pays_fee setting.
For invoice.expired / invoice.failed most payment fields will be null.
Signature verification
The signature is
sha256=HMAC_SHA256(webhook_secret, raw_body). Compute it over
the raw request body (not the parsed JSON) and compare with
X-Renovax-Signature using a constant-time comparison.
Node.js (Express):
import crypto from 'crypto';
import express from 'express';
const app = express();
const SECRET = process.env.RENOVAX_WEBHOOK_SECRET;
// IMPORTANT: capture the raw body — body-parser JSON would stringify it back.
app.post('/webhooks/renovax',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.header('X-Renovax-Signature') || '';
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET)
.update(req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(400).json({ error: 'bad_signature' });
}
const event = JSON.parse(req.body.toString());
// handle event.event, event.invoice, event.payment …
res.json({ ok: true });
});
PHP:
$secret = getenv('RENOVAX_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_RENOVAX_SIGNATURE'] ?? '';
$expect = 'sha256=' . hash_hmac('sha256', $body, $secret);
if (!hash_equals($expect, $sig)) { http_response_code(400); exit; }
$event = json_decode($body, true);
// …
Python (Flask):
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ['RENOVAX_WEBHOOK_SECRET'].encode()
@app.post('/webhooks/renovax')
def hook():
body = request.get_data()
expected = 'sha256=' + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get('X-Renovax-Signature', '')):
abort(400)
# request.get_json() is safe now
return {'ok': True}
Retries & idempotency
- Respond with any 2xx within 10 seconds to acknowledge. Anything else (non-2xx, timeout, connection error) triggers a retry.
- Retries follow exponential backoff:
30s, 2m, 10m, 1h, 6h, 24h(6 attempts total). After that the delivery is marked failed — inspect it in your merchant dashboard under Webhooks. - The
X-Renovax-Event-Idis stable across retries. Use it as an idempotency key on your side: persist the event-id the first time you process the event and skip duplicates. - We never send the same event twice successfully — but network retries, user-initiated redeliveries, and the Test webhook button can all replay the same event-id.
Rotate your secret anytime from the merchant's Webhooks tab. The next delivery after the rotation is signed with the new key.
Errors
Errors return a JSON body with an error key and appropriate HTTP status:
{ "error": "invalid_token_type", "message": "This endpoint requires a merchant token." }
401— missing or invalid token402— negative credit balance403— token type or role not authorized404— resource not found409— duplicate (e.g. pending invoice with same amount)422— validation or business-rule failure