Skip to main content

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

1. Configure Your Endpoint

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)

Request Format

Every callback includes these headers:
HeaderDescription
Content-Typeapplication/json
X-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

EventTrigger
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

EventTrigger
payment.receivedPayment confirmed
payment.failedPayment failed

KYC Events

EventTrigger
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:
AttemptDelay
1Immediate
21 minute
35 minutes
415 minutes
51 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

StatusDescription
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:
ParameterTypeDescription
statusstringFilter by status: pending, delivered, failed
eventTypestringFilter by event type (e.g., order.completed)
limitnumberNumber of results per page (default: 50, max: 100)
offsetnumberPagination 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

IssueSolution
No webhooks receivedCheck webhookUrl is set on project
Signature invalidVerify using raw request body, check signing key
Events delayedCheck your endpoint returns 2xx quickly
Missing eventsCheck dead-letter queue for failed deliveries
Failed deliveriesUse the delivery management API to inspect errors and retry