Getting Started
Implementation Guides
API Reference
- Sender
- Provider
- General
Sender API Integration
Integrate with Paycrest using the Sender API for initiating payment orders via REST API.
ℹ️ Note: Paycrest currently supports stablecoin-to-fiat (offramp) transactions only. Fiat-to-stablecoin (onramp) is coming in Q3 2025.
In this guide, we demonstrate how to enable off-ramps for users with the Sender API. The main difference between the Sender API and the Gateway contract is that users get a receiving address to pay for rather than connecting their non-custodial wallets. This means users can off-ramp directly from any wallet.
Getting Started
Step 1: Obtain API Credentials
First, you need to get the Client ID
from your sender dashboard.
Visit your Sender Dashboard to retrieve your Client ID
and Client Secret
. If you’re a new user, sign up as a “sender” and complete our Know-Your-Business (KYB) process. Your Client Secret
should always be kept secret - we’ll get to this later in the article.
Step 2: Configure Tokens
Head over to the settings page of your Sender Dashboard to configure the feePercent
, feeAddress
, and refundAddress
across the tokens and blockchain networks you intend to use.
Step 3: Authentication Setup
Include your Client ID
in the “API-Key” header of every request you make to Paycrest Offramp API.
const headers = {
"API-Key": "208a4aef-1320-4222-82b4-e3bca8781b4b",
};
This is because requests without a valid API key will fail with status code 401: Unauthorized
.
Creating Payment Orders
Basic Order Creation
// Create a payment order
const orderData = {
amount: '100',
token: 'USDT',
network: 'base',
rate: '1.0',
recipient: {
institution: 'GTB',
accountIdentifier: '1234567890',
accountName: 'John Doe',
currency: 'NGN',
memo: 'Salary payment for January 2024' // Optional: Purpose/narration for the payment
},
reference: 'payment-123',
returnAddress: '0x1234567890123456789012345678901234567890'
};
const response = await fetch("https://api.paycrest.io/v1/orders", {
method: "POST",
headers: {
"API-Key": "YOUR_CLIENT_ID",
"Content-Type": "application/json"
},
body: JSON.stringify(orderData)
});
const order = await response.json();
console.log('Order created:', order);
// Create a payment order
const orderData = {
amount: '100',
token: 'USDT',
network: 'base',
rate: '1.0',
recipient: {
institution: 'GTB',
accountIdentifier: '1234567890',
accountName: 'John Doe',
currency: 'NGN',
memo: 'Salary payment for January 2024' // Optional: Purpose/narration for the payment
},
reference: 'payment-123',
returnAddress: '0x1234567890123456789012345678901234567890'
};
const response = await fetch("https://api.paycrest.io/v1/orders", {
method: "POST",
headers: {
"API-Key": "YOUR_CLIENT_ID",
"Content-Type": "application/json"
},
body: JSON.stringify(orderData)
});
const order = await response.json();
console.log('Order created:', order);
import requests
# Create a payment order
order_data = {
"amount": "100",
"token": "USDT",
"network": "base",
"rate": "1.0",
"recipient": {
"institution": "GTB",
"accountIdentifier": "1234567890",
"accountName": "John Doe",
"currency": "NGN",
"memo": "Salary payment for January 2024" # Optional: Purpose/narration for the payment
},
"reference": "payment-123",
"returnAddress": "0x1234567890123456789012345678901234567890"
}
response = requests.post(
"https://api.paycrest.io/v1/orders",
headers={
"API-Key": "YOUR_CLIENT_ID",
"Content-Type": "application/json"
},
json=order_data
)
order = response.json()
print('Order created:', order)
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
// Create a payment order
func createPaymentOrder() error {
orderData := map[string]interface{}{
"amount": "100",
"token": "USDT",
"network": "base",
"rate": "1.0",
"recipient": map[string]interface{}{
"institution": "GTB",
"accountIdentifier": "1234567890",
"accountName": "John Doe",
"currency": "NGN",
"memo": "Salary payment for January 2024", // Optional: Purpose/narration for the payment
},
"reference": "payment-123",
"returnAddress": "0x1234567890123456789012345678901234567890",
}
jsonData, _ := json.Marshal(orderData)
req, _ := http.NewRequest("POST", "https://api.paycrest.io/v1/orders", bytes.NewBuffer(jsonData))
req.Header.Set("API-Key", "YOUR_CLIENT_ID")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("Order created:", string(body))
return nil
}
Handle the Response
// The response includes important information
const {
id, // Order ID for tracking
receiveAddress, // Address to send tokens to
validUntil, // Expiration time
senderFee, // Fee amount
transactionFee // Network transaction fee
} = order;
// Store the order ID for tracking
await saveOrderToDatabase(order.id, order);
// The response includes important information
const {
id, // Order ID for tracking
receiveAddress, // Address to send tokens to
validUntil, // Expiration time
senderFee, // Fee amount
transactionFee // Network transaction fee
} = order;
// Store the order ID for tracking
await saveOrderToDatabase(order.id, order);
# The response includes important information
order_id = order['id'] # Order ID for tracking
receive_address = order['receiveAddress'] # Address to send tokens to
valid_until = order['validUntil'] # Expiration time
sender_fee = order['senderFee'] # Fee amount
transaction_fee = order['transactionFee'] # Network transaction fee
# Store the order ID for tracking
save_order_to_database(order['id'], order)
// The response includes important information
type OrderResponse struct {
ID string `json:"id"`
ReceiveAddress string `json:"receiveAddress"`
ValidUntil string `json:"validUntil"`
SenderFee float64 `json:"senderFee"`
TransactionFee float64 `json:"transactionFee"`
}
// Parse the response
var orderResp OrderResponse
json.Unmarshal(body, &orderResp)
// Store the order ID for tracking
saveOrderToDatabase(orderResp.ID, orderResp)
Send Tokens to Receive Address
// Using viem to send tokens
import { createPublicClient, createWalletClient, http, getContract, parseUnits } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
const publicClient = createPublicClient({
chain: base,
transport: http('https://mainnet.base.org')
});
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const walletClient = createWalletClient({
account,
chain: base,
transport: http('https://mainnet.base.org')
});
// USDT contract on Base
const usdtContract = getContract({
address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', // USDT on Base
abi: [{
name: 'transfer',
type: 'function',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'nonpayable'
}],
publicClient,
walletClient
});
// Send tokens to the receive address
const { request } = await usdtContract.simulate.transfer({
args: [order.receiveAddress, parseUnits(order.amount, 6)] // USDT has 6 decimals
});
const hash = await walletClient.writeContract(request);
console.log('Transaction hash:', hash);
// Using viem to send tokens
import { createPublicClient, createWalletClient, http, getContract, parseUnits } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
const publicClient = createPublicClient({
chain: base,
transport: http('https://mainnet.base.org')
});
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const walletClient = createWalletClient({
account,
chain: base,
transport: http('https://mainnet.base.org')
});
// USDT contract on Base
const usdtContract = getContract({
address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', // USDT on Base
abi: [{
name: 'transfer',
type: 'function',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'nonpayable'
}],
publicClient,
walletClient
});
// Send tokens to the receive address
const { request } = await usdtContract.simulate.transfer({
args: [order.receiveAddress, parseUnits(order.amount, 6)] // USDT has 6 decimals
});
const hash = await walletClient.writeContract(request);
console.log('Transaction hash:', hash);
# Using web3.py to send tokens
from web3 import Web3
from eth_account import Account
import os
# Connect to Base network
w3 = Web3(Web3.HTTPProvider('https://mainnet.base.org'))
# Load private key
account = Account.from_key(os.getenv('PRIVATE_KEY'))
# USDT contract on Base
usdt_address = '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb'
usdt_abi = [{
'name': 'transfer',
'type': 'function',
'inputs': [
{'name': 'to', 'type': 'address'},
{'name': 'amount', 'type': 'uint256'}
],
'outputs': [{'name': '', 'type': 'bool'}],
'stateMutability': 'nonpayable'
}]
usdt_contract = w3.eth.contract(address=usdt_address, abi=usdt_abi)
# Send tokens to the receive address
amount_wei = w3.to_wei(order['amount'], 'ether') # USDT has 6 decimals
tx = usdt_contract.functions.transfer(
order['receiveAddress'],
amount_wei
).build_transaction({
'from': account.address,
'gas': 100000,
'gasPrice': w3.eth.gas_price,
'nonce': w3.eth.get_transaction_count(account.address)
})
# Sign and send transaction
signed_tx = w3.eth.account.sign_transaction(tx, account.key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
print('Transaction hash:', tx_hash.hex())
// Using go-ethereum to send tokens
package main
import (
"context"
"fmt"
"log"
"math/big"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func sendTokens(order map[string]interface{}) error {
// Connect to Base network
client, err := ethclient.Dial("https://mainnet.base.org")
if err != nil {
return err
}
// Load private key
privateKey, err := crypto.HexToECDSA(os.Getenv("PRIVATE_KEY"))
if err != nil {
return err
}
// USDT contract on Base
usdtAddress := common.HexToAddress("0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb")
// Transfer function signature
transferSig := crypto.Keccak256([]byte("transfer(address,uint256)"))
transferSig = transferSig[:4]
// Prepare transaction data
amount := new(big.Int)
amount.SetString(order["amount"].(string), 10)
amount.Mul(amount, new(big.Int).Exp(big.NewInt(10), big.NewInt(6), nil)) // USDT has 6 decimals
toAddress := common.HexToAddress(order["receiveAddress"].(string))
// Encode function call
data := append(transferSig, toAddress.Bytes()...)
data = append(data, common.LeftPadBytes(amount.Bytes(), 32)...)
// Get nonce
nonce, err := client.PendingNonceAt(context.Background(), crypto.PubkeyToAddress(privateKey.PublicKey))
if err != nil {
return err
}
// Create transaction
tx := &types.Transaction{
To: &usdtAddress,
Value: big.NewInt(0),
Gas: 100000,
GasPrice: big.NewInt(20000000000), // 20 gwei
Nonce: nonce,
Data: data,
}
// Sign transaction
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(8453)), privateKey)
if err != nil {
return err
}
// Send transaction
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
return err
}
fmt.Printf("Transaction hash: %s\n", signedTx.Hash().Hex())
return nil
}
Order Status Monitoring
Your status can either be any of the following:
payment_order.pending
- Order created, waiting for provider assignmentpayment_order.validated
- Funds have been sent to recipient’s bank/mobile network (value transfer confirmed)payment_order.expired
- Order expired without completionpayment_order.settled
- Order fully completed on blockchainpayment_order.refunded
- Funds refunded to sender
Once you deploy your server and get the endpoint, you can listen to payment order events by configuring the Webhook URL in your dashboard settings. We trigger various events based on the status of the payment order. Our webhook events are sent exponentially until 24 hours from when the first one is sent.
If pending, your frontend would have to continue polling till it gets back a conclusive response - either validated
, expired
, or refunded
.
You can tell your user the transaction was successful (or provide value) at the validated
status, since this indicates funds have been sent to the recipient’s bank/mobile network. The settled
status occurs when the provider has received the stablecoin on-chain, which is separate from the sender-to-recipient money flow.
Webhook Implementation
// Server setup and webhook endpoint
app.post("/webhook", async (req, res, next) => {
const signature = req.get("X-Paycrest-Signature");
if (!signature) return false;
if (!verifyPaycrestSignature(req.body, signature, process.env.CLIENT_SECRET!)) {
return res.status(401).send("Invalid signature");
}
console.log("Webhook received:", req.body);
try {
const transaction = await prisma.transaction.create({
data: {
id: req.body.data.id,
status: req.body.event,
},
});
res.json({ data: transaction });
} catch (err) {
next(err);
}
res.status(200).send("Webhook received");
});
function verifyPaycrestSignature(requestBody, signatureHeader, secretKey) {
const calculatedSignature = calculateHmacSignature(requestBody, secretKey);
return signatureHeader === calculatedSignature;
}
function calculateHmacSignature(data, secretKey) {
const crypto = require('crypto');
const key = Buffer.from(secretKey);
const hash = crypto.createHmac("sha256", key);
hash.update(data);
return hash.digest("hex");
}
// Server setup and webhook endpoint
app.post("/webhook", async (req, res, next) => {
const signature = req.get("X-Paycrest-Signature");
if (!signature) return false;
if (!verifyPaycrestSignature(req.body, signature, process.env.CLIENT_SECRET!)) {
return res.status(401).send("Invalid signature");
}
console.log("Webhook received:", req.body);
try {
const transaction = await prisma.transaction.create({
data: {
id: req.body.data.id,
status: req.body.event,
},
});
res.json({ data: transaction });
} catch (err) {
next(err);
}
res.status(200).send("Webhook received");
});
function verifyPaycrestSignature(requestBody, signatureHeader, secretKey) {
const calculatedSignature = calculateHmacSignature(requestBody, secretKey);
return signatureHeader === calculatedSignature;
}
function calculateHmacSignature(data, secretKey) {
const crypto = require('crypto');
const key = Buffer.from(secretKey);
const hash = crypto.createHmac("sha256", key);
hash.update(data);
return hash.digest("hex");
}
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import os
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
signature = request.headers.get("X-Paycrest-Signature")
if not signature:
return jsonify({"error": "No signature"}), 401
if not verify_paycrest_signature(request.data, signature, os.getenv("CLIENT_SECRET")):
return jsonify({"error": "Invalid signature"}), 401
print("Webhook received:", request.json)
try:
transaction = Transaction(
id=request.json["data"]["id"],
status=request.json["event"]
)
db.session.add(transaction)
db.session.commit()
return jsonify({"data": transaction.to_dict()})
except Exception as e:
return jsonify({"error": str(e)}), 500
def verify_paycrest_signature(request_body, signature_header, secret_key):
calculated_signature = calculate_hmac_signature(request_body, secret_key)
return signature_header == calculated_signature
def calculate_hmac_signature(data, secret_key):
key = secret_key.encode('utf-8')
hash_obj = hmac.new(key, data, hashlib.sha256)
return hash_obj.hexdigest()
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type WebhookPayload struct {
Event string `json:"event"`
Data struct {
ID string `json:"id"`
} `json:"data"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-Paycrest-Signature")
if signature == "" {
http.Error(w, "No signature", http.StatusUnauthorized)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}
if !verifyPaycrestSignature(body, signature, os.Getenv("CLIENT_SECRET")) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
fmt.Println("Webhook received:", string(body))
var payload WebhookPayload
if err := json.Unmarshal(body, &payload); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
transaction := Transaction{
ID: payload.Data.ID,
Status: payload.Event,
}
if err := db.Create(&transaction).Error; err != nil {
http.Error(w, "Database error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Webhook received"))
}
func verifyPaycrestSignature(requestBody []byte, signatureHeader, secretKey string) bool {
calculatedSignature := calculateHmacSignature(requestBody, secretKey)
return signatureHeader == calculatedSignature
}
func calculateHmacSignature(data []byte, secretKey string) string {
key := []byte(secretKey)
hash := hmac.New(sha256.New, key)
hash.Write(data)
return hex.EncodeToString(hash.Sum(nil))
}
Webhook URLs are configured through the Sender Dashboard settings, not via API. Visit your dashboard to set up your webhook endpoint URL.
Polling Implementation
// Status polling endpoint
app.get("/transactions/:id", async (req, res, next) => {
const { id } = req.params;
const transaction = await prisma.transaction.findUnique({
where: { id },
});
res.json({ data: transaction ? transaction : 'Non-existent transaction' });
});
// Poll for status updates
async function checkOrderStatus(orderId) {
try {
const response = await fetch(`https://api.paycrest.io/v1/orders/${orderId}`, {
headers: { "API-Key": "YOUR_CLIENT_ID" }
});
const order = await response.json();
switch (order.status) {
case 'pending':
console.log('Order is pending provider assignment');
break;
case 'validated':
console.log('Funds have been sent to recipient\'s bank/mobile network (value transfer confirmed)');
await handleOrderValidated(order);
break;
case 'settled':
console.log('Order has been settled on blockchain');
await handleOrderSettled(order);
break;
case 'refunded':
console.log('Order was refunded to the sender');
await handleOrderRefunded(order);
break;
case 'expired':
console.log('Order expired without completion');
await handleOrderExpired(order);
break;
}
return order;
} catch (error) {
console.error('Error checking order status:', error);
throw error;
}
}
// Status polling endpoint
app.get("/transactions/:id", async (req, res, next) => {
const { id } = req.params;
const transaction = await prisma.transaction.findUnique({
where: { id },
});
res.json({ data: transaction ? transaction : 'Non-existent transaction' });
});
// Poll for status updates
async function checkOrderStatus(orderId) {
try {
const response = await fetch(`https://api.paycrest.io/v1/orders/${orderId}`, {
headers: { "API-Key": "YOUR_CLIENT_ID" }
});
const order = await response.json();
switch (order.status) {
case 'pending':
console.log('Order is pending provider assignment');
break;
case 'validated':
console.log('Funds have been sent to recipient\'s bank/mobile network (value transfer confirmed)');
await handleOrderValidated(order);
break;
case 'settled':
console.log('Order has been settled on blockchain');
await handleOrderSettled(order);
break;
case 'refunded':
console.log('Order was refunded to the sender');
await handleOrderRefunded(order);
break;
case 'expired':
console.log('Order expired without completion');
await handleOrderExpired(order);
break;
}
return order;
} catch (error) {
console.error('Error checking order status:', error);
throw error;
}
}
# Status polling endpoint
@app.route("/transactions/<id>", methods=["GET"])
def get_transaction(id):
transaction = Transaction.query.filter_by(id=id).first()
if transaction:
return jsonify({"data": transaction.to_dict()})
else:
return jsonify({"data": "Non-existent transaction"})
# Poll for status updates
def check_order_status(order_id):
try:
response = requests.get(
f"https://api.paycrest.io/v1/orders/{order_id}",
headers={"API-Key": "YOUR_CLIENT_ID"}
)
order = response.json()
if order['status'] == 'pending':
print('Order is pending provider assignment')
elif order['status'] == 'validated':
print('Funds have been sent to recipient\'s bank/mobile network (value transfer confirmed)')
handle_order_validated(order)
elif order['status'] == 'settled':
print('Order has been settled on blockchain')
handle_order_settled(order)
elif order['status'] == 'refunded':
print('Order was refunded to the sender')
handle_order_refunded(order)
elif order['status'] == 'expired':
print('Order expired without completion')
handle_order_expired(order)
return order
except Exception as e:
print('Error checking order status:', str(e))
raise e
// Status polling endpoint
func getTransactionHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
var transaction Transaction
result := db.Where("id = ?", id).First(&transaction)
if result.Error != nil {
json.NewEncoder(w).Encode(map[string]interface{}{
"data": "Non-existent transaction",
})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"data": transaction,
})
}
// Poll for status updates
type OrderStatus struct {
Status string `json:"status"`
CancellationReason string `json:"cancellationReason,omitempty"`
}
func checkOrderStatus(orderID string) (*OrderStatus, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("https://api.paycrest.io/v1/orders/%s", orderID), nil)
if err != nil {
return nil, err
}
req.Header.Set("API-Key", "YOUR_CLIENT_ID")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var order OrderStatus
if err := json.Unmarshal(body, &order); err != nil {
return nil, err
}
switch order.Status {
case "pending":
fmt.Println("Order is pending provider assignment")
case "validated":
fmt.Println("Funds have been sent to recipient's bank/mobile network (value transfer confirmed)")
handleOrderValidated(order)
case "settled":
fmt.Println("Order has been settled on blockchain")
handleOrderSettled(order)
case "refunded":
fmt.Println("Order was refunded to the sender")
handleOrderRefunded(order)
case "expired":
fmt.Println("Order expired without completion")
handleOrderExpired(order)
}
return &order, nil
}
Error Handling
API Error Handling
async function createPaymentOrder(orderData) {
try {
const response = await fetch("https://api.paycrest.io/v1/orders", {
method: "POST",
headers: {
"API-Key": "YOUR_CLIENT_ID",
"Content-Type": "application/json"
},
body: JSON.stringify(orderData)
});
if (!response.ok) {
if (response.status === 400) {
// Validation error
const validationErrors = await response.json();
throw new Error(`Validation failed: ${JSON.stringify(validationErrors)}`);
} else if (response.status === 401) {
// Authentication error
throw new Error('Invalid API key');
} else if (response.status === 429) {
// Rate limit exceeded
throw new Error('Rate limit exceeded. Please try again later.');
} else {
// Other errors
throw new Error(`API error: ${response.statusText}`);
}
}
return await response.json();
} catch (error) {
console.error('Error creating payment order:', error);
throw error;
}
}
Retry Logic
async function createOrderWithRetry(orderData, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await createPaymentOrder(orderData);
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
// Wait before retrying (exponential backoff)
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Production Considerations
Security Best Practices
// Use environment variables for sensitive data
const config = {
apiKey: process.env.PAYCREST_API_KEY,
webhookSecret: process.env.PAYCREST_WEBHOOK_SECRET
};
// Validate webhook signatures
app.post('/webhooks/paycrest', async (req, res) => {
const signature = req.headers['x-paycrest-signature'];
if (!validateWebhookSignature(req.body, signature, config.webhookSecret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
// ...
});
Database Integration
// Example with PostgreSQL
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
async function saveOrder(order) {
const query = `
INSERT INTO payment_orders (
id, amount, token, network, status,
recipient_institution, recipient_account, recipient_name,
created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`;
await pool.query(query, [
order.id,
order.amount,
order.token,
order.network,
order.status,
order.recipient.institution,
order.recipient.accountIdentifier,
order.recipient.accountName,
new Date(),
new Date()
]);
}
Testing
Unit Tests
// Using Jest for testing
describe('Paycrest Sender API Integration', () => {
test('should create payment order successfully', async () => {
const orderData = {
amount: '100',
token: 'USDT',
network: 'base',
recipient: {
institution: 'GTB',
accountIdentifier: '1234567890',
accountName: 'Test User',
currency: 'NGN'
}
};
const order = await createPaymentOrder(orderData);
expect(order.id).toBeDefined();
expect(order.receiveAddress).toBeDefined();
expect(order.status).toBe('pending');
});
test('should handle API errors gracefully', async () => {
const invalidOrderData = {
amount: '-100', // Invalid amount
token: 'USDT',
network: 'base'
};
await expect(createPaymentOrder(invalidOrderData))
.rejects
.toThrow('Validation failed');
});
});
Deployment Checklist
Before going live, ensure you have:
- KYC verification completed
- API credentials generated and secured
- Webhook endpoints configured and tested
- Error handling implemented
- Monitoring and logging set up
- Database schema created
- Rate limiting configured
- Security measures implemented
- Testing completed with small amounts
- Documentation updated
This backend structure can be done in any custom way depending on your app as long as the webhook validates and stores the correct payload sent to it.
Choose this method if you want a simple, offchain integration for your platform or business.
- Getting Started
- Step 1: Obtain API Credentials
- Step 2: Configure Tokens
- Step 3: Authentication Setup
- Creating Payment Orders
- Basic Order Creation
- Handle the Response
- Send Tokens to Receive Address
- Order Status Monitoring
- Webhook Implementation
- Polling Implementation
- Error Handling
- API Error Handling
- Retry Logic
- Production Considerations
- Security Best Practices
- Database Integration
- Testing
- Unit Tests
- Deployment Checklist