Skip to main content

Partner Callback URLs

Callback URLs (webhooks) are the recommended integration method for tracking transactions. Instead of polling the API, your server receives HTTP POST requests whenever transaction events occur.

Why Use Callback URLs?

Real-Time Updates

Receive notifications instantly when transactions complete, fail, or are refunded

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

Configure your webhook URL through the Partner Dashboard at dashboard.nowramp.com under Settings > Webhooks.

2. Save Your Signing Key

When you set a webhook URL, a signing key is generated. Copy it from the Partner Dashboard under Settings > Webhooks.
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., transaction.completed)
X-Webhook-Delivery-IdUnique delivery ID

Payload Structure

{
  "id": "evt_txn_abc123",
  "type": "transaction.completed",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "order": {
      "id": "txn_xyz789",
      "type": "buy",
      "status": "completed",
      "source": {
        "currency": "USD",
        "amount": "100.00"
      },
      "destination": {
        "currency": "ETH",
        "amount": "0.0523",
        "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f1bD21"
      },
      "externalOrderId": "gw_order_456",
      "customerId": "cust_uuid",
      "externalCustomerId": "user_123",
      "metadata": {
        "provider": "provider_a",
        "providerOrderId": "gw_order_456",
        "email": "user@example.com",
        "partnerMetadata": {
          "orderId": "your-internal-order-123"
        }
      },
      "createdAt": "2025-01-15T10:25:00.000Z",
      "completedAt": "2025-01-15T10:30:00.000Z"
    }
  }
}
The metadata.partnerMetadata object contains any custom data you passed via session metadata or checkout-intent partnerMetadata. Use it to correlate webhook events back to your internal orders. See the Webhooks guide for details.

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;
  }

  const signaturePayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signaturePayload)
    .digest('hex');

  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);
  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:
    now = int(time.time())
    if abs(now - int(timestamp)) > 300:
        return False

    signature_payload = f"{timestamp}.{payload}"
    expected = hmac.new(
        secret.encode(),
        signature_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

Event Types

EventTrigger
transaction.pendingCheckout created, awaiting payment
transaction.processingPayment received, processing crypto transfer
transaction.completedCrypto delivered to wallet
transaction.failedTransaction failed
transaction.cancelledTransaction cancelled by user or system
transaction.refundedPayment refunded

Example: transaction.completed

{
  "id": "evt_txn_abc123",
  "type": "transaction.completed",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "order": {
      "id": "txn_xyz789",
      "type": "buy",
      "status": "completed",
      "previousStatus": "processing",
      "source": {
        "currency": "USD",
        "amount": "100.00"
      },
      "destination": {
        "currency": "ETH",
        "amount": "0.0523",
        "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f1bD21"
      },
      "externalOrderId": "gw_order_456",
      "customerId": "cust_uuid",
      "externalCustomerId": "user_123",
      "metadata": {
        "provider": "provider_a",
        "providerOrderId": "gw_order_456",
        "email": "user@example.com",
        "partnerMetadata": {
          "orderId": "your-internal-order-123"
        }
      },
      "createdAt": "2025-01-15T10:25:00.000Z",
      "completedAt": "2025-01-15T10:30:00.000Z"
    }
  }
}

Example: transaction.failed

{
  "id": "evt_txn_def456",
  "type": "transaction.failed",
  "timestamp": "2025-01-15T10:28:00.000Z",
  "data": {
    "order": {
      "id": "txn_abc789",
      "type": "buy",
      "status": "failed",
      "previousStatus": "processing",
      "source": {
        "currency": "EUR",
        "amount": "50.00"
      },
      "destination": {
        "currency": "USDC",
        "amount": "0",
        "address": "0x123..."
      },
      "externalOrderId": "gw_order_789",
      "customerId": "cust_uuid",
      "externalCustomerId": "user_456",
      "metadata": {
        "provider": "provider_b",
        "providerOrderId": "gw_order_789",
        "partnerMetadata": {
          "orderId": "your-internal-order-456"
        }
      },
      "createdAt": "2025-01-15T10:25:00.000Z"
    }
  }
}

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.

Delivery Statuses

StatusDescription
pendingAwaiting delivery — queued and will be sent shortly
deliveredSuccessfully delivered — your endpoint returned a 2xx response
failedDelivery failed after retries — moved to dead-letter queue

List Webhook Deliveries

curl "https://api.nowramp.com/v1/webhooks/events?status=failed" \
  -H "X-API-Key: sk_live_your_secret_key"
Query Parameters:
ParameterTypeDescription
statusstringFilter by status: pending, delivered, failed
eventTypestringFilter by event type (e.g., transaction.completed)
limitnumberResults per page (default: 50, max: 100)
offsetnumberPagination offset

Retry Failed Delivery

curl -X POST "https://api.nowramp.com/v1/webhooks/events/del_def456/retry" \
  -H "X-API-Key: sk_live_your_secret_key"
You can only retry deliveries with failed status.

Partner Dashboard

You can also manage webhook deliveries from the Partner Dashboard:
  • View delivery history with filtering
  • Inspect full payload and response details
  • Retry failed deliveries with one click
  • Monitor delivery health and success rates

Best Practices

1. Respond Quickly

Return a 2xx response as fast as possible. Process events asynchronously:
app.post('/webhooks/ramp', (req, res) => {
  res.status(200).json({ received: true });
  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) {
  const existing = await db.webhookEvents.findOne({ eventId: event.id });
  if (existing) return;

  await handleEvent(event);
  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. Monitor Failures

Set up alerts for failed webhook deliveries. Use the delivery management API or Partner Dashboard to monitor delivery health.

Testing

Sandbox Events

In sandbox mode, trigger test events:
curl -X POST https://api.sandbox.nowramp.com/v1/webhooks/test \
  -H "X-API-Key: sk_test_your_secret_key" \
  -d '{"eventType": "transaction.completed"}'

Local Development

Use ngrok or similar to expose your local server:
ngrok http 3000
# Use the https URL as your webhook URL

Troubleshooting

IssueSolution
No webhooks receivedCheck webhook URL is set in project settings
Signature invalidVerify using raw request body, check signing key
Events delayedCheck your endpoint returns 2xx quickly
Missing eventsCheck dead-letter queue for failed deliveries