Server Integration
The SDK's onCreateInvoice callback hits your server, which signs the request with HMAC and forwards it to the OozooPay API. The server returns the resulting ID (invoiceId for pay(), withdrawalId for transfer()) back to the SDK.
For the full HMAC signature spec see Authentication.
The OOZOO_SECRET_KEY (sk_xxx) must never reach the browser. Keep it server-only via environment variables and never embed it in frontend code or commits.
Endpoints
| SDK call | HTTP | Path | Required body | Returns |
|---|---|---|---|---|
pay() | POST | /api/invoices | price, chainId, tokenAddress, sender | data.invoiceId |
transfer() | POST | /api/invoices/withdrawals | price, chainId, tokenAddress, receiver | data.withdrawalId |
Both share the same HMAC signing scheme; only the path, the address field (sender vs receiver), and the response key differ.
Create Invoice (Pay) Endpoint
- Next.js
- Express (Node.js)
- PHP
- Python (Flask)
// app/api/create-invoice/route.ts
import * as crypto from 'crypto';
export async function POST(req: Request) {
const { price, unit, chainId, tokenAddress, sender } = await req.json();
const clientKey = process.env.OOZOO_CLIENT_KEY!;
const secretKey = process.env.OOZOO_SECRET_KEY!;
const hmacKey = crypto.createHash('sha256').update(secretKey).digest('hex');
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = 'POST';
const path = '/api/invoices';
const body = JSON.stringify({ price, unit, chainId, tokenAddress, sender });
const message = `${timestamp}.${method}.${path}.${body}`;
const signature = crypto.createHmac('sha256', hmacKey).update(message).digest('hex');
const res = await fetch('https://api.oozoopay.com/api/invoices', {
method,
headers: {
'Content-Type': 'application/json',
'X-Client-Key': clientKey,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body,
});
const result = await res.json();
return Response.json({ invoiceId: result.data.invoiceId });
}
import express from 'express';
import * as crypto from 'crypto';
const app = express();
app.use(express.json());
app.post('/api/create-invoice', async (req, res) => {
const { price, unit, chainId, tokenAddress, sender } = req.body;
const clientKey = process.env.OOZOO_CLIENT_KEY!;
const secretKey = process.env.OOZOO_SECRET_KEY!;
const hmacKey = crypto.createHash('sha256').update(secretKey).digest('hex');
const timestamp = Math.floor(Date.now() / 1000).toString();
const path = '/api/invoices';
const body = JSON.stringify({ price, unit, chainId, tokenAddress, sender });
const signature = crypto
.createHmac('sha256', hmacKey)
.update(`${timestamp}.POST.${path}.${body}`)
.digest('hex');
const apiRes = await fetch(`https://api.oozoopay.com${path}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Key': clientKey,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body,
});
const result = await apiRes.json();
res.json({ invoiceId: result.data.invoiceId });
});
<?php
// create-invoice.php
header('Content-Type: application/json');
$clientKey = getenv('OOZOO_CLIENT_KEY');
$secretKey = getenv('OOZOO_SECRET_KEY');
$apiUrl = 'https://api.oozoopay.com';
$body = file_get_contents('php://input'); // raw JSON from SDK callback
$timestamp = (string) time();
$method = 'POST';
$path = '/api/invoices';
$hmacKey = hash('sha256', $secretKey);
$message = "$timestamp.$method.$path.$body";
$signature = hash_hmac('sha256', $message, $hmacKey);
$ch = curl_init("$apiUrl$path");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-Client-Key: $clientKey",
"X-Timestamp: $timestamp",
"X-Signature: $signature",
],
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
echo json_encode(['invoiceId' => $result['data']['invoiceId']]);
# app.py
import hashlib
import hmac
import json
import os
import time
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/create-invoice', methods=['POST'])
def create_invoice():
payload = request.get_json()
body_dict = {
'price': payload['price'],
'unit': payload['unit'],
'chainId': payload['chainId'],
'tokenAddress': payload['tokenAddress'],
'sender': payload['sender'],
}
client_key = os.environ['OOZOO_CLIENT_KEY']
secret_key = os.environ['OOZOO_SECRET_KEY']
body = json.dumps(body_dict, separators=(',', ':'))
timestamp = str(int(time.time()))
path = '/api/invoices'
hmac_key = hashlib.sha256(secret_key.encode()).hexdigest()
message = f'{timestamp}.POST.{path}.{body}'
signature = hmac.new(hmac_key.encode(), message.encode(), hashlib.sha256).hexdigest()
res = requests.post(
f'https://api.oozoopay.com{path}',
data=body,
headers={
'Content-Type': 'application/json',
'X-Client-Key': client_key,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
)
result = res.json()
return jsonify({'invoiceId': result['data']['invoiceId']})
Create Withdrawal (Transfer) Endpoint
The SDK's transfer() callback receives receiver (instead of sender) and your server should call the withdrawals endpoint, returning withdrawalId.
This endpoint creates a withdrawal request in REQUESTED status — it is not an immediate on-chain transfer. A merchant admin must subsequently approve it (with 2FA) in the dashboard for the funds to actually move.
- Next.js
- Express (Node.js)
- PHP
- Python (Flask)
// app/api/create-withdrawal/route.ts
import * as crypto from 'crypto';
export async function POST(req: Request) {
const { price, unit, chainId, tokenAddress, receiver } = await req.json();
const clientKey = process.env.OOZOO_CLIENT_KEY!;
const secretKey = process.env.OOZOO_SECRET_KEY!;
const hmacKey = crypto.createHash('sha256').update(secretKey).digest('hex');
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = 'POST';
const path = '/api/invoices/withdrawals';
const body = JSON.stringify({ price, unit, chainId, tokenAddress, receiver });
const message = `${timestamp}.${method}.${path}.${body}`;
const signature = crypto.createHmac('sha256', hmacKey).update(message).digest('hex');
const res = await fetch(`https://api.oozoopay.com${path}`, {
method,
headers: {
'Content-Type': 'application/json',
'X-Client-Key': clientKey,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body,
});
const result = await res.json();
return Response.json({ withdrawalId: result.data.withdrawalId });
}
import express from 'express';
import * as crypto from 'crypto';
const app = express();
app.use(express.json());
app.post('/api/create-withdrawal', async (req, res) => {
const { price, unit, chainId, tokenAddress, receiver } = req.body;
const clientKey = process.env.OOZOO_CLIENT_KEY!;
const secretKey = process.env.OOZOO_SECRET_KEY!;
const hmacKey = crypto.createHash('sha256').update(secretKey).digest('hex');
const timestamp = Math.floor(Date.now() / 1000).toString();
const path = '/api/invoices/withdrawals';
const body = JSON.stringify({ price, unit, chainId, tokenAddress, receiver });
const signature = crypto
.createHmac('sha256', hmacKey)
.update(`${timestamp}.POST.${path}.${body}`)
.digest('hex');
const apiRes = await fetch(`https://api.oozoopay.com${path}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Key': clientKey,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body,
});
const result = await apiRes.json();
res.json({ withdrawalId: result.data.withdrawalId });
});
<?php
// create-withdrawal.php
header('Content-Type: application/json');
$clientKey = getenv('OOZOO_CLIENT_KEY');
$secretKey = getenv('OOZOO_SECRET_KEY');
$apiUrl = 'https://api.oozoopay.com';
$body = file_get_contents('php://input'); // raw JSON from SDK callback
$timestamp = (string) time();
$method = 'POST';
$path = '/api/invoices/withdrawals';
$hmacKey = hash('sha256', $secretKey);
$message = "$timestamp.$method.$path.$body";
$signature = hash_hmac('sha256', $message, $hmacKey);
$ch = curl_init("$apiUrl$path");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-Client-Key: $clientKey",
"X-Timestamp: $timestamp",
"X-Signature: $signature",
],
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
echo json_encode(['withdrawalId' => $result['data']['withdrawalId']]);
# app.py
import hashlib
import hmac
import json
import os
import time
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/create-withdrawal', methods=['POST'])
def create_withdrawal():
payload = request.get_json()
body_dict = {
'price': payload['price'],
'unit': payload['unit'],
'chainId': payload['chainId'],
'tokenAddress': payload['tokenAddress'],
'receiver': payload['receiver'],
}
client_key = os.environ['OOZOO_CLIENT_KEY']
secret_key = os.environ['OOZOO_SECRET_KEY']
body = json.dumps(body_dict, separators=(',', ':'))
timestamp = str(int(time.time()))
path = '/api/invoices/withdrawals'
hmac_key = hashlib.sha256(secret_key.encode()).hexdigest()
message = f'{timestamp}.POST.{path}.{body}'
signature = hmac.new(hmac_key.encode(), message.encode(), hashlib.sha256).hexdigest()
res = requests.post(
f'https://api.oozoopay.com{path}',
data=body,
headers={
'Content-Type': 'application/json',
'X-Client-Key': client_key,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
)
result = res.json()
return jsonify({'withdrawalId': result['data']['withdrawalId']})
Common Pitfalls
- Body must be byte-identical: The string passed to HMAC must match the body sent over the wire exactly. If you re-serialize between hashing and sending (key reordering, whitespace), the signature breaks.
- Timestamp drift: Server timestamp must be within ±5 minutes of OozooPay's clock. Sync NTP if you see signature errors.
- Returning the wrong field: The OozooPay API responds with
{ success: true, data: { invoiceId | withdrawalId, ... } }. Forward the corresponding field —data.invoiceIdfor pay,data.withdrawalIdfor transfer.