Partner Callback URLs
Callback URLs (webhooks) are the preferred integration method for most partners. Instead of polling the API, your server receives HTTP POST requests whenever events occur.
Why Use Callback URLs?
Real-Time Updates Receive notifications instantly when orders complete, KYC approves, or payments process
Reduced API Calls No need to poll for status updates - events are pushed to you
Reliable Delivery Automatic retries with exponential backoff ensure delivery
Secure HMAC signatures verify authenticity of every request
Setup
Set your callback URL when creating or updating your project:
curl -X PATCH https://api.nowramp.com/v1/projects/YOUR_PROJECT_ID \
-H "Authorization: Bearer admin_token" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://partner-app.com/webhooks/ramp"
}'
You can also configure your webhook URL through the Partner Dashboard at dashboard.nowramp.com under Project Settings.
2. Save Your Signing Key
When you set a webhook URL, a signing key is generated. Retrieve it from the project details:
curl https://api.nowramp.com/v1/projects/YOUR_PROJECT_ID \
-H "Authorization: Bearer admin_token"
Response includes:
{
"id" : "project_xyz789" ,
"webhookUrl" : "https://partner-app.com/webhooks/ramp" ,
"webhookSigningKey" : "whsec_abc123..."
}
Store the signing key securely. It’s used to verify webhook authenticity.
3. Implement Your Endpoint
Your endpoint must:
Accept POST requests
Return 2xx status within 30 seconds
Verify the signature (strongly recommended)
Every callback includes these headers:
Header Description Content-Typeapplication/jsonX-Webhook-TimestampUnix timestamp (seconds) X-Webhook-SignatureHMAC-SHA256 signature X-Webhook-EventEvent type (e.g., order.completed) X-Webhook-Delivery-IdUnique delivery ID
Payload Structure
{
"id" : "evt_abc123xyz" ,
"type" : "order.completed" ,
"timestamp" : "2024-01-15T10:30:00.000Z" ,
"data" : {
"order" : {
"id" : "order_xyz789" ,
"type" : "buy" ,
"status" : "completed" ,
"source" : {
"currency" : "USD" ,
"amount" : "100.00"
},
"destination" : {
"currency" : "ETH" ,
"amount" : "0.0234" ,
"address" : "0x742d35Cc..."
},
"customerId" : "cust_abc123" ,
"externalOrderId" : "your_order_123" ,
"createdAt" : "2024-01-15T10:25:00.000Z" ,
"completedAt" : "2024-01-15T10:30:00.000Z"
}
}
}
Signature Verification
Always verify signatures to ensure requests are from NowRamp.
Algorithm
signature = HMAC-SHA256(
key: your_signing_key,
message: "{timestamp}.{json_payload}"
)
Node.js Implementation
const crypto = require ( 'crypto' );
function verifyWebhookSignature ( payload , signature , timestamp , secret ) {
// Check timestamp is recent (within 5 minutes)
const now = Math . floor ( Date . now () / 1000 );
if ( Math . abs ( now - parseInt ( timestamp )) > 300 ) {
return false ; // Request is too old
}
// Compute expected signature
const signaturePayload = ` ${ timestamp } . ${ payload } ` ;
const expectedSignature = crypto
. createHmac ( 'sha256' , secret )
. update ( signaturePayload )
. digest ( 'hex' );
// Timing-safe comparison
try {
return crypto . timingSafeEqual (
Buffer . from ( signature ),
Buffer . from ( expectedSignature )
);
} catch {
return false ;
}
}
// Express middleware
app . post ( '/webhooks/ramp' , express . raw ({ type: 'application/json' }), ( req , res ) => {
const signature = req . headers [ 'x-webhook-signature' ];
const timestamp = req . headers [ 'x-webhook-timestamp' ];
const payload = req . body . toString ();
if ( ! verifyWebhookSignature ( payload , signature , timestamp , process . env . WEBHOOK_SECRET )) {
return res . status ( 401 ). json ({ error: 'Invalid signature' });
}
const event = JSON . parse ( payload );
// Process asynchronously - respond quickly
processWebhookAsync ( event );
res . status ( 200 ). json ({ received: true });
});
Python Implementation
import hmac
import hashlib
import time
def verify_webhook_signature ( payload : str , signature : str , timestamp : str , secret : str ) -> bool :
# Check timestamp is recent
now = int (time.time())
if abs (now - int (timestamp)) > 300 :
return False
# Compute expected signature
signature_payload = f " { timestamp } . { payload } "
expected = hmac.new(
secret.encode(),
signature_payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
Event Types
Order Events
Event Trigger order.createdNew order created order.pendingOrder awaiting payment order.processingOrder is being processed order.completedOrder successfully completed order.failedOrder failed order.cancelledOrder was cancelled order.refundedOrder was refunded
Payment Events
Event Trigger payment.receivedPayment confirmed payment.failedPayment failed
KYC Events
Event Trigger kyc.approvedCustomer KYC approved kyc.rejectedCustomer KYC rejected kyc.pending_reviewKYC sent for manual review
Event Payloads
order.completed
{
"id" : "evt_abc123" ,
"type" : "order.completed" ,
"timestamp" : "2024-01-15T10:30:00.000Z" ,
"data" : {
"order" : {
"id" : "order_xyz789" ,
"type" : "buy" ,
"status" : "completed" ,
"previousStatus" : "processing" ,
"source" : {
"currency" : "USD" ,
"amount" : "100.00"
},
"destination" : {
"currency" : "ETH" ,
"amount" : "0.0234" ,
"address" : "0x742d35Cc6634C0532925a3b844Bc9e7595f5bE21"
},
"externalOrderId" : "your_order_123" ,
"customerId" : "cust_abc123" ,
"createdAt" : "2024-01-15T10:25:00.000Z" ,
"completedAt" : "2024-01-15T10:30:00.000Z"
}
}
}
kyc.approved
{
"id" : "evt_def456" ,
"type" : "kyc.approved" ,
"timestamp" : "2024-01-15T09:00:00.000Z" ,
"data" : {
"caseId" : "kyc_abc123" ,
"customerId" : "cust_xyz789" ,
"tier" : "basic" ,
"status" : "approved"
}
}
payment.received
{
"id" : "evt_ghi789" ,
"type" : "payment.received" ,
"timestamp" : "2024-01-15T10:28:00.000Z" ,
"data" : {
"orderId" : "order_xyz789" ,
"payment" : {
"amount" : "100.00" ,
"currency" : "USD" ,
"method" : "card" ,
"externalRefId" : "pay_abc123"
}
}
}
Retry Policy
Failed deliveries are automatically retried with exponential backoff:
Attempt Delay 1 Immediate 2 1 minute 3 5 minutes 4 15 minutes 5 1 hour
After 5 failed attempts, the webhook is marked as failed and moved to the dead-letter queue.
Webhook Delivery Management
NowRamp provides partner-facing endpoints to monitor and manage webhook deliveries. You can list delivery history, inspect details, and manually retry failed deliveries.
Delivery Statuses
Status Description pendingAwaiting delivery - the webhook is queued and will be sent shortly deliveredSuccessfully delivered - your endpoint returned a 2xx response failedDelivery failed after retries - moved to dead-letter queue
API Endpoints
List Webhook Deliveries
Retrieve a paginated list of webhook deliveries for your project.
GET https://api.nowramp.com/v1/webhooks/events
curl https://api.nowramp.com/v1/webhooks/events \
-H "X-API-Key: your_api_key"
Query Parameters:
Parameter Type Description statusstring Filter by status: pending, delivered, failed eventTypestring Filter by event type (e.g., order.completed) limitnumber Number of results per page (default: 50, max: 100) offsetnumber Pagination offset
Response:
{
"data" : [
{
"id" : "del_abc123" ,
"eventId" : "evt_xyz789" ,
"eventType" : "order.completed" ,
"status" : "delivered" ,
"attempts" : 1 ,
"lastAttemptAt" : "2024-01-15T10:30:01Z" ,
"nextRetryAt" : null ,
"createdAt" : "2024-01-15T10:30:00Z" ,
"deliveredAt" : "2024-01-15T10:30:01Z"
},
{
"id" : "del_def456" ,
"eventId" : "evt_abc123" ,
"eventType" : "order.failed" ,
"status" : "failed" ,
"attempts" : 5 ,
"lastAttemptAt" : "2024-01-15T12:30:00Z" ,
"nextRetryAt" : null ,
"createdAt" : "2024-01-15T10:30:00Z" ,
"deliveredAt" : null ,
"failedAt" : "2024-01-15T12:30:00Z" ,
"lastError" : "Connection timeout"
}
],
"total" : 150 ,
"limit" : 50 ,
"offset" : 0
}
Get Delivery Details
Retrieve detailed information about a specific webhook delivery, including the full payload and delivery attempts.
GET https://api.nowramp.com/v1/webhooks/events/:id
curl https://api.nowramp.com/v1/webhooks/events/del_abc123 \
-H "X-API-Key: your_api_key"
Response:
{
"id" : "del_abc123" ,
"eventId" : "evt_xyz789" ,
"eventType" : "order.completed" ,
"status" : "delivered" ,
"webhookUrl" : "https://partner-app.com/webhooks/ramp" ,
"payload" : {
"id" : "evt_xyz789" ,
"type" : "order.completed" ,
"timestamp" : "2024-01-15T10:30:00.000Z" ,
"data" : {
"order" : {
"id" : "order_xyz789" ,
"type" : "buy" ,
"status" : "completed"
}
}
},
"attempts" : [
{
"attemptNumber" : 1 ,
"attemptedAt" : "2024-01-15T10:30:01Z" ,
"statusCode" : 200 ,
"duration" : 245 ,
"success" : true
}
],
"createdAt" : "2024-01-15T10:30:00Z" ,
"deliveredAt" : "2024-01-15T10:30:01Z"
}
Retry Failed Delivery
Manually retry a failed webhook delivery. This requeues the webhook for immediate delivery.
POST https://api.nowramp.com/v1/webhooks/events/:id/retry
curl -X POST https://api.nowramp.com/v1/webhooks/events/del_def456/retry \
-H "X-API-Key: your_api_key"
Response:
{
"id" : "del_def456" ,
"status" : "pending" ,
"message" : "Webhook delivery queued for retry"
}
You can only retry deliveries with failed status. Attempting to retry a pending or delivered webhook will return an error.
Code Examples
List Failed Deliveries (Node.js)
async function listFailedDeliveries ( apiKey ) {
const response = await fetch (
'https://api.nowramp.com/v1/webhooks/events?status=failed' ,
{
headers: {
'X-API-Key' : apiKey ,
},
}
);
const { data } = await response . json ();
console . log ( `Found ${ data . length } failed deliveries` );
for ( const delivery of data ) {
console . log ( `- ${ delivery . id } : ${ delivery . eventType } ( ${ delivery . attempts } attempts)` );
console . log ( ` Last error: ${ delivery . lastError } ` );
}
return data ;
}
Retry All Failed Deliveries (Node.js)
async function retryAllFailedDeliveries ( apiKey ) {
// Get all failed deliveries
const response = await fetch (
'https://api.nowramp.com/v1/webhooks/events?status=failed' ,
{
headers: {
'X-API-Key' : apiKey ,
},
}
);
const { data : failedDeliveries } = await response . json ();
console . log ( `Retrying ${ failedDeliveries . length } failed deliveries...` );
const results = await Promise . allSettled (
failedDeliveries . map ( async ( delivery ) => {
const retryResponse = await fetch (
`https://api.nowramp.com/v1/webhooks/events/ ${ delivery . id } /retry` ,
{
method: 'POST' ,
headers: {
'X-API-Key' : apiKey ,
},
}
);
if ( ! retryResponse . ok ) {
throw new Error ( `Failed to retry ${ delivery . id } ` );
}
return delivery . id ;
})
);
const succeeded = results . filter (( r ) => r . status === 'fulfilled' ). length ;
const failed = results . filter (( r ) => r . status === 'rejected' ). length ;
console . log ( `Retry complete: ${ succeeded } succeeded, ${ failed } failed` );
}
Monitor Deliveries (Python)
import requests
def get_delivery_stats ( api_key : str ) -> dict :
"""Get webhook delivery statistics."""
headers = { 'X-API-Key' : api_key}
base_url = 'https://api.nowramp.com/v1/webhooks/events'
stats = {}
for status in [ 'pending' , 'delivered' , 'failed' ]:
response = requests.get(
f ' { base_url } ?status= { status } &limit=1' ,
headers = headers
)
data = response.json()
stats[status] = data[ 'total' ]
return stats
def retry_failed_delivery ( api_key : str , delivery_id : str ) -> bool :
"""Retry a specific failed delivery."""
response = requests.post(
f 'https://api.nowramp.com/v1/webhooks/events/ { delivery_id } /retry' ,
headers = { 'X-API-Key' : api_key}
)
return response.ok
Partner Dashboard
You can also manage webhook deliveries directly from the Partner Dashboard at dashboard.nowramp.com .
From the dashboard, you can:
View delivery history with filtering and search
Inspect full payload and response details
Retry failed deliveries with one click
Monitor delivery health and success rates
Set up alerts for delivery failures
The Partner Dashboard provides a visual interface for managing webhooks without writing code. Use it for debugging, monitoring, and occasional manual retries.
Best Practices
1. Respond Quickly
Return a 2xx response as fast as possible. Process events asynchronously:
app . post ( '/webhooks/ramp' , ( req , res ) => {
// Respond immediately
res . status ( 200 ). json ({ received: true });
// Process async
queue . add ( 'process-webhook' , req . body );
});
2. Handle Duplicates
Events may be delivered more than once. Use the event id for idempotency:
async function processWebhook ( event ) {
// Check if already processed
const existing = await db . webhookEvents . findOne ({ eventId: event . id });
if ( existing ) {
return ; // Already processed
}
// Process event
await handleEvent ( event );
// Mark as processed
await db . webhookEvents . insert ({
eventId: event . id ,
processedAt: new Date ()
});
}
3. Use HTTPS
Webhook endpoints must use HTTPS in production.
4. Verify Signatures
Always verify the X-Webhook-Signature header.
5. Log Everything
Log webhook events for debugging and audit:
function processWebhook ( event ) {
console . log ( 'Webhook received:' , {
id: event . id ,
type: event . type ,
timestamp: event . timestamp ,
});
// ...
}
6. Monitor Failures
Set up alerts for failed webhook deliveries to catch integration issues early. Use the delivery management API or Partner Dashboard to monitor delivery health.
Testing
Sandbox Events
In sandbox mode, trigger test events:
# Trigger a test webhook
curl -X POST https://api.sandbox.nowramp.com/v1/webhooks/test \
-H "X-API-Key: your_api_key" \
-d '{"eventType": "order.completed"}'
Local Development
Use ngrok or similar to expose your local server:
ngrok http 3000
# Use the https URL as your webhookUrl
Troubleshooting
Issue Solution No webhooks received Check webhookUrl is set on project Signature invalid Verify using raw request body, check signing key Events delayed Check your endpoint returns 2xx quickly Missing events Check dead-letter queue for failed deliveries Failed deliveries Use the delivery management API to inspect errors and retry