Developer docs

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, or admin role. 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
EventWhen it fires
invoice.paidPayment confirmed on-chain (crypto) or by provider webhook (Stripe/PayPal). Status confirmed.
invoice.partialPayment received but amount is below expected (within configured tolerance). Status confirmed.
invoice.overpaidPayment received but amount exceeds expected (within configured tolerance). Status confirmed.
invoice.failedInvoice auto-cancelled by anti-fraud (Tor/VPN/datacenter/Scamalytics). Status failed.
invoice.expiredTTL 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-Id is 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 token
  • 402 — negative credit balance
  • 403 — token type or role not authorized
  • 404 — resource not found
  • 409 — duplicate (e.g. pending invoice with same amount)
  • 422 — validation or business-rule failure