Swipelux
Rails/Our OnRamp/Server-side Integration

Webhooks

Webhooks provide real-time notifications about card rail order status changes, enabling you to update your system immediately when purchases complete, fail, or change status. This eliminates the need for polling and ensures your application stays synchronized with payment events.

Webhook Events

Swipelux sends webhook notifications for these card rail order events:

EventDescriptionWhen it fires
order.createdOrder was created successfullyImmediately after order creation via API
order.processingPayment processing has startedWhen user initiates payment
order.completedPurchase completed successfullyWhen crypto is transferred to user's wallet
order.failedPurchase failed during processingWhen payment or transfer fails
order.cancelledOrder was cancelledWhen user cancels or order times out

Implementation

Implement a Webhook Handler

Create a webhook endpoint that can receive POST requests from Swipelux.

We've prepared example implementations for popular server technologies:

Verify the Webhook Payload

To ensure webhook payload authenticity, we sign it using JWT (JSON Web Token).

You can verify the signature using our public key available at https://api.swipelux.com/api/merchants/webhookKeys.

// npm install jose
const { jwtVerify, importJWK } = require('jose');
 
async function getPublicKeys() {
  const response = await fetch(
    "https://api.swipelux.com/api/merchants/webhookKeys"
  );
  const { keys } = await response.json();
  return keys;
}
 
async function verifyWebhookPayload(webhookPayload) {
  const publicKeys = await getPublicKeys();
  const publicKey = await importJWK(publicKeys[0], 'ES256');
 
  const token = `${webhookPayload.protected}.${webhookPayload.payload}.${webhookPayload.signature}`;
  const { payload } = await jwtVerify(token, publicKey);
  return payload;
}

Handle the Webhook Payload

Once verified, process the webhook payload to update your system based on the order status change.

Example Webhook Payload

{
  "eventTs": 1706710725798,
  "eventType": "order.completed",
  "orderId": "ord_abc123def456",
  "externalId": "user_purchase_123",
  "status": "SUCCESS",
  "order": {
    "amounts": {
      "from": {
        "amount": 100.00,
        "currency": "USD"
      },
      "to": {
        "amount": 99.2,
        "currency": "USDC"
      }
    },
    "fees": {
      "total": 0.80,
      "breakdown": [
        {
          "type": "processing",
          "amount": 0.50,
          "description": "Card processing fee"
        },
        {
          "type": "network", 
          "amount": 0.30,
          "description": "Blockchain network fee"
        }
      ]
    },
    "targetAddress": "0x1234567890abcdef1234567890abcdef12345678",
    "network": "polygon",
    "transactions": [
      {
        "type": "crypto_transfer",
        "transactionId": "0xabc123def456...",
        "publicUrl": "https://polygonscan.com/tx/0xabc123def456...",
        "status": "confirmed"
      }
    ],
    "user": {
      "email": "user@example.com",
      "phone": "+1234567890"
    }
  }
}

Payload Schema

PropTypeDefault
order
object
-
status
enum
-
externalId?
string
-
orderId
string
-
eventType
string
-
eventTs
number
-

Configure Webhook Endpoint

Once your webhook handler is implemented and deployed, configure the endpoint URL in the Merchant Panel.

Important Requirements:

  • Endpoint must be publicly accessible over HTTPS
  • Must respond with HTTP 200 status for successful processing
  • Should respond within 10 seconds to avoid timeout
  • Must handle duplicate events idempotently

Implementation Patterns

Order Status Updates

async function handleCardRailWebhook(payload) {
  const { eventType, orderId, externalId, status, order } = payload;
  
  try {
    // Find the order in your system
    const localOrder = await findOrderByExternalId(externalId) || 
                       await findOrderBySwipeluxId(orderId);
    
    if (!localOrder) {
      console.warn(`Order not found: ${orderId} (${externalId})`);
      return;
    }
    
    // Update order status
    await updateOrderStatus(localOrder.id, {
      status,
      swipeluxOrderId: orderId,
      completedAt: eventType === 'order.completed' ? new Date() : null,
      transactionHash: order.transactions?.[0]?.transactionId,
      transactionUrl: order.transactions?.[0]?.publicUrl,
      actualCryptoAmount: order.amounts.to.amount,
      fees: order.fees.total
    });
    
    // Trigger post-completion actions
    if (eventType === 'order.completed') {
      await handlePurchaseCompletion(localOrder, order);
    }
    
    console.log(`Card rail order ${orderId} updated to ${status}`);
    
  } catch (error) {
    console.error('Webhook processing error:', error);
    throw error; // Re-throw to indicate processing failure
  }
}

User Notifications

async function handlePurchaseCompletion(localOrder, swipeluxOrder) {
  const user = await getUserById(localOrder.userId);
  
  // Send success notification
  await sendEmail(user.email, 'purchase-completed', {
    amount: swipeluxOrder.amounts.from.amount,
    currency: swipeluxOrder.amounts.from.currency,
    cryptoAmount: swipeluxOrder.amounts.to.amount,
    cryptoCurrency: swipeluxOrder.amounts.to.currency,
    network: swipeluxOrder.network,
    transactionUrl: swipeluxOrder.transactions[0]?.publicUrl,
    walletAddress: swipeluxOrder.targetAddress
  });
  
  // Update user's crypto balance if tracking internally
  await updateUserBalance(user.id, {
    currency: swipeluxOrder.amounts.to.currency,
    amount: swipeluxOrder.amounts.to.amount,
    network: swipeluxOrder.network
  });
  
  // Trigger any post-purchase workflows
  await triggerPostPurchaseWorkflow(localOrder, swipeluxOrder);
}

Error Handling

async function handleFailedOrder(localOrder, swipeluxOrder) {
  // Update order status
  await updateOrderStatus(localOrder.id, {
    status: 'FAILED',
    failureReason: swipeluxOrder.failureReason,
    failedAt: new Date()
  });
  
  // Notify user of failure
  const user = await getUserById(localOrder.userId);
  await sendEmail(user.email, 'purchase-failed', {
    orderId: localOrder.id,
    amount: swipeluxOrder.amounts.from.amount,
    currency: swipeluxOrder.amounts.from.currency,
    failureReason: swipeluxOrder.failureReason,
    supportUrl: 'https://support.yourcompany.com'
  });
  
  // Log for manual review if needed
  await logFailedOrder(localOrder.id, swipeluxOrder);
}

Best Practices

Idempotency

Always handle webhooks idempotently to avoid duplicate processing:

async function processWebhookIdempotently(webhookId, payload) {
  // Check if we've already processed this webhook
  const existingWebhook = await findWebhookById(webhookId);
  if (existingWebhook) {
    console.log(`Webhook ${webhookId} already processed`);
    return;
  }
  
  // Record the webhook before processing
  await recordWebhook(webhookId, payload);
  
  // Process the webhook
  await handleCardRailWebhook(payload);
  
  // Mark as successfully processed
  await markWebhookProcessed(webhookId);
}

Retry Logic

Implement exponential backoff for failed webhook processing:

async function processWebhookWithRetry(payload, maxRetries = 3) {
  let attempt = 0;
  
  while (attempt < maxRetries) {
    try {
      await handleCardRailWebhook(payload);
      return; // Success, exit
    } catch (error) {
      attempt++;
      if (attempt >= maxRetries) {
        throw new Error(`Webhook processing failed after ${maxRetries} attempts: ${error.message}`);
      }
      
      const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Monitoring

Track webhook processing metrics:

async function monitorWebhookProcessing(eventType, processingTimeMs, success) {
  // Log to your monitoring system
  await logMetric('webhook.processing_time', processingTimeMs, {
    eventType,
    success: success.toString()
  });
  
  if (!success) {
    await alertingSystem.send({
      type: 'webhook_processing_failure',
      eventType,
      timestamp: new Date().toISOString()
    });
  }
}

Testing Webhooks

During development, use tools like ngrok to expose your local webhook endpoint:

# Install ngrok
npm install -g ngrok
 
# Expose your local server
ngrok http 3000
 
# Use the provided HTTPS URL in Merchant Panel
# Example: https://abc123.ngrok.io/webhooks/swipelux

Next Steps

  • Implementation examples: Check out our Node.js and PHP webhook handler examples
  • Error monitoring: Set up alerting for failed webhook processing
  • Analytics integration: Use webhook data to power conversion and revenue analytics