osuvox Payments API

The integrated payments API for server-side checkout flows, webhooks, and split payouts.

Overview

osuvox.net is a cryptocurrency payment processor that enables you to accept Bitcoin (BTC), Litecoin (LTC), and Dogecoin (DOGE) payments through a simple REST API.

Base URL: https://www.osuvox.net/api/v1

Monitoring runs internally via the worker/queue system and is not exposed as a public API endpoint. Status changes are communicated to merchants exclusively via webhooks.

TXID guarantee: The transaction ID is guaranteed in payment.confirmed and payment.completed webhooks. payment.detected may have a null txid until confirmations progress.

Looking for hosted checkout or a no-code option? See osuvox Hosted Checkout or osuvox Links.

Hosted Checkout vs Full Integration

Hosted Checkout

  • Redirect to payment_url
  • No custom checkout UI required
  • Fastest path to launch
  • Fulfillment via webhooks

Full Integration

  • Render address/QR yourself
  • Full UI control
  • More implementation work
  • Fulfillment via webhooks

Key Features

  • Unique addresses - Each payment gets its own receiving address
  • Real-time webhooks - Get notified instantly when payments arrive
  • Split payments - Automatically distribute funds to multiple wallets
  • Internal monitoring - Monitoring runs via the worker/queue system; webhooks are the only external signal
  • Live-only API - Use small amounts during setup and build carefully
  • Hosted payment pages - Beautiful checkout pages with QR codes

Confirmation Times

CoinDefault ConfirmationsApproximate Time
BTC3~30 minutes
LTC6~15 minutes
DOGE6~6 minutes

Quick Start

Get up and running in 5 minutes:

Step 1: Get API Keys

Sign up at osuvox.net and copy your API keys from Dashboard → API Keys

Set OSUVOX_API_BASE and OSUVOX_WEBHOOK_URL in your server environment if provided by your deployment platform.

Step 2: Configure Webhook URL

Set your webhook endpoint in Dashboard → Settings. Copy your webhook secret.

Step 3: Create a Payment

curl -X POST https://www.osuvox.net/api/v1/payments \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "0.001",
    "coin": "BTC",
    "external_id": "order_123",
    "webhook_url": "https://your-site.com/webhooks/osuvox"
  }'

Always pass external_id. Use webhook_url unless you set OSUVOX_WEBHOOK_URL as a default.

Step 4: Handle Webhooks

When payment is confirmed, we'll POST to your webhook URL:

// Your webhook endpoint receives:
{
  "id": "evt_abc123",
  "type": "payment.confirmed",
  "data": {
    "payment": {
      "id": "pay_xyz789",
      "external_id": "order_123",
      "status": "confirmed"
    }
  }
}

Authentication

Authenticate requests by including your API key in the Authorization header:

Authorization: Bearer sk_live_your_api_key

Live Mode

sk_live_...

Live-only API. Use small amounts during setup and build carefully.

Security: Never expose API keys in client-side code. Make all API calls from your server.

Implementation Notes (Important)

  • OSUVOX_API_BASE: If set, use it as the base API URL. Do not hard-code the URL.
  • OSUVOX_WEBHOOK_URL: If set, use it as the default webhook URL unless explicitly overridden per payment.
  • Fulfillment trigger: Use payment.confirmed or payment.completed. If you need split payout finality, use payment.completed.
  • TXID guarantee: Fulfillment should rely on confirmed/completed events because they always include txid.
  • Dedupe: Store webhook event IDs in your database. In-memory dedupe is not sufficient for retries or multi-instance deployments.
  • external_id: Treat external_id as the canonical linkage between your order and Osuvox payment for all payment flows.
  • Reconciliation: GET /payments is for reconciliation only. Webhooks are the primary signal; avoid polling as a primary mechanism.

Get Prices

GET /api/v1/prices

Retrieves the current cached exchange rates for all supported cryptocurrencies in USD. This endpoint is public and does not require authentication.

Rate Limiting

Strictly limited to 1 request per minute per IP address. This endpoint is intended for checking rates before initiating payments.

Example Request

curl https://www.osuvox.net/api/v1/prices

Response

{
  "data": {
    "BTC": 43250.25,
    "LTC": 72.45,
    "DOGE": 0.078
  },
  "currency": "USD",
  "timestamp": 1705152000000
}

Create Payment

POST /api/v1/payments

Creates a new payment and generates a unique cryptocurrency address.

Request Parameters

ParameterTypeRequiredDescription
amountstring | numberYesPayment amount in cryptocurrency
coinstringYesBTC, LTC, or DOGE
external_idstringNoYour order/reference ID (max 255 chars). Treat as required for fulfillment and reconciliation.
descriptionstringNoPayment description (max 500 chars)
metadataobjectNoCustom key-value data. Passed through to webhooks.
webhook_urlstringNoOverride default webhook URL for this payment
expires_innumberNoSeconds until expiration (default: 600, max: 86400)
confirmationsnumberNoOverride default confirmation requirement

Example Request

curl -X POST https://www.osuvox.net/api/v1/payments \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "0.001",
    "coin": "BTC",
    "external_id": "order_12345",
    "description": "Premium subscription",
    "metadata": {
      "customer_id": "cust_abc",
      "plan": "premium"
    }
  }'

Response

{
  "id": "pay_abc123def456",
  "status": "pending",
  "coin": "BTC",
  "amount": "0.00100000",
  "amount_received": "0",
  "address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
  "external_id": "order_12345",
  "description": "Premium subscription",
  "payment_url": "https://www.osuvox.net/pay/pay_abc123def456",
  "qr_code_url": "https://www.osuvox.net/api/qr/pay_abc123def456",
  "confirmations_required": 3,
  "confirmations": 0,
  "expires_at": "2026-01-11T22:30:00Z",
  "created_at": "2026-01-11T21:30:00Z",
  "metadata": {
    "customer_id": "cust_abc",
    "plan": "premium"
  }
}

Response Fields

FieldTypeDescription
idstringUnique payment ID (pay_xxx)
addressstringUnique receiving address for this payment
payment_urlstringHosted payment page URL - redirect customers here (webhooks remain the source of truth)
qr_code_urlstringQR code image URL (PNG, 300x300)
expires_atstringISO 8601 timestamp when payment expires

Get Payment

GET /api/v1/payments/{id}

Retrieves details for a specific payment.

Example Request

curl https://www.osuvox.net/api/v1/payments/pay_abc123def456 \
  -H "Authorization: Bearer sk_live_xxx"

Response

{
  "id": "pay_abc123def456",
  "status": "confirmed",
  "coin": "BTC",
  "amount_requested": "0.00100000",
  "amount_received": "0.00100000",
  "address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
  "txid": "a1b2c3d4e5f6789012345678901234567890abcdef",
  "external_id": "order_12345",
  "description": "Premium subscription",
  "confirmations_required": 3,
  "confirmations": 6,
  "metadata": { "customer_id": "cust_abc" },
  "payment_url": "https://www.osuvox.net/pay/pay_abc123def456",
  "qr_code_url": "https://www.osuvox.net/api/qr/pay_abc123def456",
  "expires_at": "2026-01-11T22:30:00Z",
  "created_at": "2026-01-11T21:30:00Z",
  "detected_at": "2026-01-11T21:45:00Z",
  "confirmed_at": "2026-01-11T22:15:00Z",
  "completed_at": null
}

List Payments

GET /api/v1/payments

Returns a paginated list of payments.

Query Parameters

ParameterTypeDefaultDescription
limitnumber20Results per page (max 100)
offsetnumber0Pagination offset
statusstringFilter by status
coinstringFilter by cryptocurrency
external_idstringFind payment by your order ID
fromstringFilter by created_at ≥ (ISO 8601)
tostringFilter by created_at ≤ (ISO 8601)

Example: Find by Order ID

curl "https://www.osuvox.net/api/v1/payments?external_id=order_12345" \
  -H "Authorization: Bearer sk_live_xxx"

Response

{
  "data": [
    {
      "id": "pay_abc123def456",
      "status": "confirmed",
      "coin": "BTC",
      "amount_requested": "0.00100000",
      "amount_received": "0.00100000",
      "external_id": "order_12345",
      "created_at": "2026-01-11T21:30:00Z"
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0
}

Payment Statuses

StatusDescriptionAction
pendingPayment created, awaiting transactionShow payment page to customer
detectingTransaction detected (0 confirmations)Show "Payment received, confirming..."
confirmingTransaction confirming (1+ confirmations)Show confirmation progress
confirmedRequired confirmations reachedFulfill the order
completedSplit payments executedRecord payout transactions
expiredPayment window closedCancel order, offer retry
failedError occurredContact support

Webhooks

Webhooks notify your server when payment events occur. These events are generated by internal monitoring (no public monitoring endpoint). Configure your webhook URL in Dashboard → Settings.

For reliability, store event.id in your database and ignore duplicates on retry.

Webhook Payload Format

All webhooks use a Stripe-like nested structure with unique event IDs for deduplication:

{
  "id": "evt_abc123def456789",
  "type": "payment.confirmed",
  "created_at": "2026-01-11T22:15:00Z",
  "data": {
    "payment": {
      "id": "pay_abc123def456",
      "status": "confirmed",
      "coin": "BTC",
      "amount": "0.00100000",
      "amount_received": "0.00100000",
      "address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
      "txid": "a1b2c3d4e5f6...",
      "external_id": "order_12345",
      "description": "Premium subscription",
      "confirmations": 3,
      "confirmations_required": 3,
      "metadata": {
        "customer_id": "cust_abc",
        "plan": "premium"
      },
      "expires_at": "2026-01-11T22:30:00Z",
      "created_at": "2026-01-11T21:30:00Z",
      "detected_at": "2026-01-11T21:45:00Z",
      "confirmed_at": "2026-01-11T22:15:00Z",
      "completed_at": null
    }
  }
}

TXID requirement: payment.confirmed always includes data.payment.txid. payment.completed includes data.payment.txid and payout txid values. payment.detected can be null. If multiple UTXOs are involved, data.payment.metadata.txids may include the full list.

Fallback: If your handler ever receives a confirmed/completed webhook without a txid, defer fulfillment and reconcile with GET /payments/{id}. This should be rare but keeps your integration safe.

Data Persistence: Your external_id and metadata provided during payment creation are guaranteed to be returned in the data.payment object of every webhook.

HTTP Headers

HeaderDescription
X-Osuvox-SignatureTimestamped HMAC signature for verification
X-Osuvox-EventEvent type (e.g., payment.confirmed)
X-Osuvox-DeliveryUnique delivery ID

Persistent Dedupe

Store webhook event.id in a durable table. In-memory dedupe is not sufficient for retries or multi-instance deployments.

-- Example schema
create table osuvox_events (
  id text primary key,
  created_at timestamptz default now()
);

Webhook Events

EventDescriptionAction
payment.detectedTransaction seen (0 confirmations)Show "Payment received" (txid may be null)
payment.confirmedRequired confirmations reachedFulfill the order (txid always present)
payment.completedSplit payouts executedRecord payout transactions (payout txid always present)
payment.expiredPayment window closedCancel order
payment.failedError occurredLog error, alert team

payment.completed with Payouts

When split payments are executed, the webhook includes payout details. Each payout entry includes its broadcast txid.

{
  "id": "evt_ghi789",
  "type": "payment.completed",
  "created_at": "2026-01-11T22:20:00Z",
  "data": {
    "payment": {
      "id": "pay_abc123",
      "status": "completed"
    },
    "payouts": [
      {
        "address": "bc1q_vendor...",
        "amount": "0.00085000",
        "txid": "payout_tx_1...",
        "label": "Vendor",
        "percentage": 85
      },
      {
        "address": "bc1q_platform...",
        "amount": "0.00015000",
        "txid": "payout_tx_2...",
        "label": "Platform Fee",
        "percentage": 15
      }
    ]
  }
}

Signature Verification

All webhooks include a timestamped HMAC-SHA256 signature in the X-Osuvox-Signature header:

X-Osuvox-Signature: t=1704931200,v1=a1b2c3d4e5f6...

🔴 CRITICAL: Use Raw Request Body

You must verify the signature using the raw, unparsed bytes of the HTTP request body exactly as received from our servers. If you parse the JSON into an object and re-stringify it, the verification will fail because:

  • JSON key order is not guaranteed after parsing.
  • Whitespace differences (e.g. spaces vs no spaces) change the HMAC hash.

Retry signing: Each delivery attempt is signed with a fresh timestamp. Keep your verification tolerance short (recommended: 5 minutes) to reduce replay risk while still allowing retries.

Node.js (Express) Example

import crypto from 'crypto';

// Use express.raw() to get the Buffer for the webhook route
app.post('/webhooks/osuvox', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-osuvox-signature'];
  const payload = req.body.toString(); // <--- THIS IS THE RAW STRING
  
  if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  const event = JSON.parse(payload); // Parse ONLY after verification
  // ... process event
});

function verifySignature(payload, signatureHeader, secret) {
  const parts = signatureHeader.split(',');
  const timestamp = parts.find(p => p.startsWith('t=')).slice(2);
  const providedSig = parts.find(p => p.startsWith('v1=')).slice(3);
  
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSig = crypto.createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
    
  return crypto.timingSafeEqual(
    Buffer.from(expectedSig),
    Buffer.from(providedSig)
  );
}

SvelteKit / Next.js Example

export const POST = async ({ request }) => {
  const payload = await request.text(); // <--- GET RAW TEXT DIRECTLY
  const signature = request.headers.get('x-osuvox-signature');
  const secret = process.env.OSUVOX_WEBHOOK_SECRET;

  if (!verifySignature(payload, signature, secret)) {
    return new Response('Unauthorized', { status: 401 });
  }

  const event = JSON.parse(payload);
  // ... process event
  return new Response('OK');
};

Python Example

import hmac
import hashlib
import time

def verify_webhook_signature(payload: str, signature_header: str, secret: str) -> bool:
    parts = dict(p.split('=', 1) for p in signature_header.split(','))
    timestamp = int(parts['t'])
    provided_sig = parts['v1']
    
    # Check timestamp (5 minute tolerance)
    if abs(time.time() - timestamp) > 300:
        return False
    
    # Compute signature
    signed_payload = f"{timestamp}.{payload}"
    expected_sig = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(expected_sig, provided_sig)

# Flask handler
@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Osuvox-Signature')
    payload = request.get_data(as_text=True)
    
    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        return 'Invalid signature', 401
    
    event = request.json
    
    if event['type'] == 'payment.confirmed':
        fulfill_order(event['data']['payment']['external_id'])
    
    return 'OK', 200

Split Rules

Automatically distribute payments to multiple wallets. Perfect for marketplaces, affiliate programs, and revenue sharing.

Splits are configured in your dashboard under Splits and apply account-wide to all payments (API, hosted checkout, and direct payments).

Note: Split percentages can total up to 100. Any remainder becomes a platform gap allocation.

Minimums: The smallest split output must stay above dust thresholds after fees. The minimum transaction depends on your smallest split and network conditions.

Errors

Error Response Format

{
  "error": {
    "code": "invalid_amount",
    "message": "Amount must be a positive number",
    "param": "amount"
  }
}

HTTP Status Codes

CodeDescription
200Success
201Created
400Bad Request - Invalid parameters
401Unauthorized - Invalid API key
404Not Found - Resource doesn't exist
429Too Many Requests - Rate limited
500Server Error

Error Codes

CodeDescription
invalid_api_keyAPI key is invalid or revoked
invalid_amountAmount is invalid or out of range
invalid_coinUnsupported cryptocurrency
payment_not_foundPayment ID doesn't exist
payment_expiredPayment has already expired
rate_limit_exceededToo many requests

Rate Limits

We enforce rate limits to ensure API stability and prevent abuse.

Standard API (Authenticated)

100 / min

Per API key (Live/Test). Applies to /payments and other authenticated endpoints.

Prices API (Public)

1 / min

Per IP address. Applies to /prices endpoint.

Rate Limit Headers

HeaderDescription
X-RateLimit-LimitTotal requests allowed (100)
X-RateLimit-RemainingRequests remaining in window
X-RateLimit-ResetUnix timestamp when window resets

Webhook Retry Policy

If your webhook endpoint fails, we retry with exponential backoff:

AttemptDelay
1Immediate
22 minutes
34 minutes
48 minutes
516 minutes

After 5 failures, retry manually from Dashboard → Webhooks.

TypeScript Types

Copy these types into your TypeScript project:

// osuvox-types.ts

export type OsuvoxCoin = 'BTC' | 'LTC' | 'DOGE';

export type PaymentStatus = 
  | 'pending' 
  | 'detecting' 
  | 'confirming' 
  | 'confirmed' 
  | 'completed' 
  | 'expired' 
  | 'failed';

export type WebhookEventType = 
  | 'payment.detected'
  | 'payment.confirmed'
  | 'payment.completed'
  | 'payment.expired'
  | 'payment.failed';

export interface CreatePaymentParams {
  amount: string | number;
  coin: OsuvoxCoin;
  external_id?: string;
  description?: string;
  metadata?: Record<string, unknown>;
  webhook_url?: string;
  expires_in?: number;
  confirmations?: number;
  success_url?: string;  // Hosted checkout: redirect after payment confirmed
  cancel_url?: string;   // Hosted checkout: redirect if cancelled/expired
}

export interface GetPricesResponse {
  data: Record<OsuvoxCoin, number>;
  currency: 'USD';
  timestamp: number;
}

export interface OsuvoxPayment {
  id: string;
  status: PaymentStatus;
  coin: OsuvoxCoin;
  amount_requested: string;
  amount_received: string;
  address: string;
  txid: string | null;
  external_id: string | null;
  description: string | null;
  confirmations: number;
  confirmations_required: number;
  metadata: Record<string, unknown> | null;
  payment_url: string;
  qr_code_url: string;
  expires_at: string | null;
  created_at: string;
  detected_at: string | null;
  confirmed_at: string | null;
  completed_at: string | null;
}

export interface OsuvoxWebhookEvent {
  id: string;
  type: WebhookEventType;
  created_at: string;
  data: {
    payment: OsuvoxPayment;
    payouts?: Array<{
      address: string;
      amount: string;
      txid: string | null;
      label: string;
      percentage: number;
    }>;
  };
}

export interface OsuvoxError {
  error: {
    code: string;
    message: string;
    param?: string;
  };
}

Need Help?

Check the Webhook Logs to debug delivery issues, or contact support@osuvox.net.