{
  "event": "order.status_changed",
  "timestamp": "2024-01-15T10:45:00Z",
  "data": {
    "order_id": "ord_1234567890abcdef",
    "transaction_hash": "0xabc123def456789...",
    "status": "COMPLETED",
    "previous_status": "PROCESSING",
    "order_type": "onramp",
    "amount_fiat": 1000,
    "amount_crypto": 6.93,
    "currency": "KES",
    "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "token_symbol": "USDC",
    "network": "base",
    "wallet_address": "0x742d35Cc6634C0532925a3b8D4C0532925a3b8D4",
    "phone_number": "254712345678",
    "exchange_rate": 144.32,
    "fees": {
      "platform_fee": 10,
      "network_fee": 2.5,
      "total_fees": 12.5
    },
    "payment_details": {
      "method": "mpesa",
      "receipt_number": "QHX8K9L2M3",
      "confirmation_code": "ws_CO_123456789"
    },
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:45:00Z",
    "completed_at": "2024-01-15T10:45:00Z"
  },
  "signature": "sha256=a8b9c0d1e2f3..."
}

Webhooks

Receive real-time notifications when order statuses change. Webhooks are essential for onramp orders and recommended for all transactions.
Required for: Onramp orders (order_type: 0)Optional for: Offramp orders (order_type: 1)

How Webhooks Work

1

Configure Webhook URL

Provide a webhook URL when creating an order
2

Receive Notifications

Element Pay sends HTTP POST requests to your endpoint
3

Acknowledge Receipt

Respond with HTTP 200 to confirm receipt
4

Handle Retries

Failed webhooks are retried with exponential backoff

Webhook Payload

Element Pay sends a POST request to your webhook URL with the following payload:
{
  "event": "order.status_changed",
  "timestamp": "2024-01-15T10:45:00Z",
  "data": {
    "order_id": "ord_1234567890abcdef",
    "transaction_hash": "0xabc123def456789...",
    "status": "COMPLETED",
    "previous_status": "PROCESSING",
    "order_type": "onramp",
    "amount_fiat": 1000,
    "amount_crypto": 6.93,
    "currency": "KES",
    "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "token_symbol": "USDC",
    "network": "base",
    "wallet_address": "0x742d35Cc6634C0532925a3b8D4C0532925a3b8D4",
    "phone_number": "254712345678",
    "exchange_rate": 144.32,
    "fees": {
      "platform_fee": 10,
      "network_fee": 2.5,
      "total_fees": 12.5
    },
    "payment_details": {
      "method": "mpesa",
      "receipt_number": "QHX8K9L2M3",
      "confirmation_code": "ws_CO_123456789"
    },
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:45:00Z",
    "completed_at": "2024-01-15T10:45:00Z"
  },
  "signature": "sha256=a8b9c0d1e2f3..."
}

Webhook Events

Implementation Examples

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

// Webhook endpoint
app.post('/webhooks/elementpay', (req, res) => {
  const signature = req.headers['x-elementpay-signature'];
  const payload = JSON.stringify(req.body);
  
  // Verify webhook signature
  if (!verifySignature(payload, signature)) {
    return res.status(401).send('Invalid signature');
  }
  
  const { event, data } = req.body;
  
  switch (event) {
    case 'order.status_changed':
      handleStatusChange(data);
      break;
    case 'order.payment_received':
      handlePaymentReceived(data);
      break;
    case 'order.crypto_sent':
      handleCryptoSent(data);
      break;
    case 'order.failed':
      handleOrderFailed(data);
      break;
    default:
      console.log(`Unknown event: ${event}`);
  }
  
  res.status(200).send('OK');
});

function verifySignature(payload, signature) {
  const expectedSignature = crypto
    .createHmac('sha256', process.env.ELEMENTPAY_WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
  
  return signature === `sha256=${expectedSignature}`;
}

function handleStatusChange(data) {
  console.log(`Order ${data.order_id} status: ${data.status}`);
  
  // Update your database
  // Send notifications to users
  // Trigger business logic
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

Security Best Practices

Always verify webhook signatures to ensure requests are from Element Pay.Use HTTPS endpoints to protect webhook data in transit.Implement idempotency to handle duplicate webhook deliveries.

Signature Verification

Every webhook includes a signature in the X-Elementpay-Signature header:
X-Elementpay-Signature: sha256=a8b9c0d1e2f3...
Verify this signature using your webhook secret:
function verifySignature(payload, signature) {
  const expectedSignature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
  
  return signature === `sha256=${expectedSignature}`;
}

Retry Logic

Element Pay retries failed webhooks with exponential backoff:
  • Immediate retry if no response
  • 1 minute after first failure
  • 5 minutes after second failure
  • 15 minutes after third failure
  • 1 hour after fourth failure
  • 6 hours after fifth failure

Testing Webhooks

Local Development

Use tools like ngrok to expose your local server:
# Install ngrok
npm install -g ngrok

# Expose local port 3000
ngrok http 3000

# Use the HTTPS URL for your webhook
# https://abc123.ngrok.io/webhooks/elementpay

Webhook Testing Tool

// Simple webhook tester
const express = require('express');
const app = express();

app.use(express.json());

app.post('/test-webhook', (req, res) => {
  console.log('Received webhook:');
  console.log(JSON.stringify(req.body, null, 2));
  console.log('Headers:', req.headers);
  
  res.status(200).send('OK');
});

app.listen(3000, () => {
  console.log('Webhook tester running on http://localhost:3000/test-webhook');
});

Troubleshooting

Next Steps