Quick Start
Get started with BITXpay in minutes. This guide will walk you through creating your first payment integration.
Prerequisites
Before you begin, ensure you have:
- A BITXpay merchant account (Sign up here)
- API credentials (API Key and Ed25519 private key)
- Basic knowledge of REST APIs
- A development environment with Node.js, Python, or your preferred language
Step 1: Get your API credentials
- Log in to your BITXpay Dashboard
- Navigate to Developers → API Keys
- Click Create API Key
- Save your API Key (
btxm_test_xxxxxxxxxx) and Ed25519 private key securely
Keep your credentials safe
Never commit API keys or private keys to version control or expose them in client-side code.
Step 2: Make your first API call
Test your credentials by fetching your account information:
bash
curl -X GET https://sandboxapi.bitxpay.com/api/v1/account \
-H "X-API-Key: btxm_xxxxxxxxxxxx" \
-H "X-API-Signature: <base64_encoded_ed25519_signature>" \
-H "X-API-Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"javascript
const axios = require('axios');
const crypto = require('crypto');
const fs = require('fs');
const apiKey = process.env.MERCHANT_API_KEY; // btxm_xxxxxxxxxxxx
const privateKey = fs.readFileSync('private-key.pem', 'utf8');
const timestamp = new Date().toISOString();
function generateSignature(privateKeyPEM, method, path, timestamp, body = '') {
const message = `${method}${path}${timestamp}${body}`;
// Ed25519 (EdDSA): pass `null` — Ed25519 hashes the message internally.
const signature = crypto.sign(null, Buffer.from(message, 'utf8'), privateKeyPEM);
return signature.toString('base64');
}
const signature = generateSignature(privateKey, 'GET', '/account', timestamp);
axios.get('https://sandboxapi.bitxpay.com/api/v1/account', {
headers: {
'X-API-Key': apiKey,
'X-API-Signature': signature,
'X-API-Timestamp': timestamp
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error));python
import requests
from cryptography.hazmat.primitives import serialization
import base64
from datetime import datetime, timezone
import os
api_key = os.environ.get('MERCHANT_API_KEY') # btxm_xxxxxxxxxxxx
# Load your Ed25519 private key
with open('private-key.pem', 'r') as f:
private_key_pem = f.read()
private_key = serialization.load_pem_private_key(
private_key_pem.encode(),
password=None,
)
timestamp = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
def generate_signature(private_key, method, path, timestamp, body=''):
message = f'{method}{path}{timestamp}{body}'
# Ed25519 (EdDSA) — no separate hash argument; Ed25519 hashes internally.
signature = private_key.sign(message.encode('utf-8'))
return base64.b64encode(signature).decode('utf-8')
signature = generate_signature(private_key, 'GET', '/account', timestamp)
response = requests.get(
'https://sandboxapi.bitxpay.com/api/v1/account',
headers={
'X-API-Key': api_key,
'X-API-Signature': signature,
'X-API-Timestamp': timestamp
}
)
print(response.json())Step 3: Create a payment
Create your first payment request:
javascript
const axios = require('axios');
const crypto = require('crypto');
const fs = require('fs');
const apiKey = process.env.MERCHANT_API_KEY;
const privateKey = fs.readFileSync('private-key.pem', 'utf8');
const timestamp = new Date().toISOString();
const method = 'POST';
const path = '/payment_links';
const body = JSON.stringify({
payment_name: 'Test Payment',
amount: 10,
currency: 'USDT',
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel'
});
function generateSignature(privateKeyPEM, method, path, timestamp, body = '') {
const message = `${method}${path}${timestamp}${body}`;
// Ed25519 (EdDSA): pass `null` — Ed25519 hashes the message internally.
const signature = crypto.sign(null, Buffer.from(message, 'utf8'), privateKeyPEM);
return signature.toString('base64');
}
const signature = generateSignature(privateKey, method, path, timestamp, body);
axios.post(`https://sandboxapi.bitxpay.com/api/v1${path}`, body, {
headers: {
'X-API-Key': apiKey,
'X-API-Signature': signature,
'X-API-Timestamp': timestamp,
'Content-Type': 'application/json'
}
})
.then(response => {
console.log('Payment created:', response.data);
console.log('Payment URL:', response.data.data.payment_url);
})
.catch(error => console.error(error));python
import requests
from cryptography.hazmat.primitives import serialization
import base64
from datetime import datetime, timezone
import json
import os
api_key = os.environ.get('MERCHANT_API_KEY')
# Load your Ed25519 private key
with open('private-key.pem', 'r') as f:
private_key_pem = f.read()
private_key = serialization.load_pem_private_key(
private_key_pem.encode(),
password=None,
)
timestamp = datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
method = 'POST'
path = '/payment_links'
body = json.dumps({
'payment_name': 'Test Payment',
'amount': 10,
'currency': 'USDT',
'success_url': 'https://yoursite.com/success',
'cancel_url': 'https://yoursite.com/cancel'
})
def generate_signature(private_key, method, path, timestamp, body=''):
message = f'{method}{path}{timestamp}{body}'
# Ed25519 (EdDSA) — no separate hash argument; Ed25519 hashes internally.
signature = private_key.sign(message.encode('utf-8'))
return base64.b64encode(signature).decode('utf-8')
signature = generate_signature(private_key, method, path, timestamp, body)
response = requests.post(
f'https://sandboxapi.bitxpay.com/api/v1{path}',
headers={
'X-API-Key': api_key,
'X-API-Signature': signature,
'X-API-Timestamp': timestamp,
'Content-Type': 'application/json'
},
data=body
)
result = response.json()
print('Payment created:', result)
print('Payment URL:', result['data']['payment_url'])Step 4: Handle webhooks
Set up a webhook endpoint to receive payment notifications:
javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
// IMPORTANT: Use express.raw() for webhook routes to preserve the raw body for signature verification
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-bitxpay-signature'];
const payload = req.body; // Raw buffer
// Verify webhook signature using HMAC-SHA256
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const event = JSON.parse(payload);
console.log('Webhook received:', event.type);
switch (event.type) {
case 'payment.completed':
console.log('Payment completed:', event.data.id);
// Update your database, fulfill order, etc.
break;
case 'payment.failed':
console.log('Payment failed:', event.data.id);
break;
}
res.status(200).send('OK');
});
app.listen(3000, () => console.log('Webhook server running on port 3000'));python
from flask import Flask, request
import hmac
import hashlib
import json
import os
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Bitxpay-Signature')
# Use raw data for signature verification
payload = request.get_data()
# Verify webhook signature using HMAC-SHA256
expected_signature = hmac.new(
os.environ['WEBHOOK_SECRET'].encode(),
payload,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_signature):
return 'Invalid signature', 401
# Process the webhook
event = json.loads(payload)
print(f'Webhook received: {event["type"]}')
if event['type'] == 'payment.completed':
print(f'Payment completed: {event["data"]["id"]}')
# Update your database, fulfill order, etc.
elif event['type'] == 'payment.failed':
print(f'Payment failed: {event["data"]["id"]}')
return 'OK', 200
if __name__ == '__main__':
app.run(port=3000)Step 5: Test in sandbox
BITXpay provides a sandbox environment for testing:
- Sandbox API:
https://sandboxapi.bitxpay.com/api/v1 - Sandbox Dashboard: sandbox.bitxpay.com
Use sandbox credentials to test your integration without real funds.
Next steps
Now that you've created your first payment, explore more features:
- Authentication - Learn about security best practices
- Webhooks - Deep dive into webhook events
- API Reference - Explore all available endpoints
Common issues
Invalid signature error
Make sure you're:
- Using the correct Ed25519 private key
- Including the timestamp in the signature (ISO 8601 format)
- Formatting the message string correctly:
${method}${path}${timestamp}${body}— e.g.POST/payment_links2026-01-31T12:00:00Z{...} - Signing with Ed25519 (do not pre-hash — Ed25519 hashes internally)
- Encoding the final signature as Base64
Webhook not receiving events
Verify that:
- Your webhook URL is publicly accessible
- You're returning a 200 status code
- Your server supports HTTPS (required for production)
Payment not completing
Check that:
- The amount is formatted correctly (string with 2 decimal places)
- The currency is supported
- The redirect URL is valid
For more troubleshooting help, see our Troubleshooting Guide.