H2H Notifications

Host-to-Host API provides real-time payment status notifications through webhooks, enabling automatic updates when payment statuses change. This guide covers webhook setup, handling, and security best practices.

Webhook Overview

Notification System

  • Real-time Updates: Automatic payment status notifications via webhooks
  • HTTP POST: Notifications sent as HTTP POST requests
  • Content Type: application/x-www-form-urlencoded
  • Requirement: Notifications only sent if notify URL is configured

Configuration

  • Dashboard Setup: Set notify URL in dashboard profile page
  • Per-Request Override: Override notify URL in individual payment requests
  • Multiple Endpoints: Configure different URLs for different notification types

Notification Fields

FieldDescription
idPayment request identifier
transactionIdPayment request transaction identifier
transactionStatusIdTransaction status: 1 – approved, 2 – declined
paymentRequestStatusIdPayment status: 1 – paid, 2 – unpaid
merchantIdMerchant identifier (from control panel)
unitPayment currency
grossAmountPayment gross amount including fees
feePayment fee amount
netAmountNet amount deposited to merchant wallet
referenceIdCustom reference details
notesPayment notes
clientIdPayment sender customer identifier
clientNamePayment sender customer name
clientEmailPayment sender customer email
clientPhonePayment sender customer phone
clientMemberIdPayment sender customer member identifier
messagePayment failure reason message
codePayment failure reason code

Example Notification

id=16772761082427695&transactionId=265111&transactionStatusId=1&paymentRequestStatusId=1&merchantId=16762420400394816&unit=USD&grossAmount=10&fee=0.5&netAmount=9.5&referenceId=12345&notes=Payment notes&clientId=16772748432912191&clientName=Client Name&clientEmail=client@email.com&clientPhone=1234567890&clientMemberId=12345&message=Stolen Card&code=008

Webhook Handler Implementation

Basic Webhook Handler

// Handle payment status notifications
app.post('/webhooks/payment-notification', (req, res) => {
  try {
    const notification = req.body;
    
    // Verify notification authenticity (implement signature verification)
    if (!verifyNotificationSignature(req)) {
      return res.status(401).send('Unauthorized');
    }
    
    // Process payment status update
    const {
      transactionId,
      merchantReference,
      status,
      amount,
      currency,
      timestamp
    } = notification;
    
    // Update your database
    updatePaymentStatus(merchantReference, {
      transactionId,
      status,
      amount,
      currency,
      updatedAt: timestamp
    });
    
    // Handle different status types
    switch (status) {
      case 'completed':
        handleSuccessfulPayment(merchantReference);
        break;
      case 'failed':
        handleFailedPayment(merchantReference, notification.errorCode);
        break;
      case 'pending':
        handlePendingPayment(merchantReference);
        break;
      case 'cancelled':
        handleCancelledPayment(merchantReference);
        break;
    }
    
    // Always return 200 OK to acknowledge receipt
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(200).send('OK'); // Still return 200 to prevent retries
  }
});

Advanced Webhook Handler

const crypto = require('crypto');

// Enhanced webhook handler with validation and processing
app.post('/webhooks/payment-notification', async (req, res) => {
  const startTime = Date.now();
  
  try {
    // Parse notification data
    const notification = parseNotificationData(req.body);
    
    // Verify webhook signature
    if (!verifyWebhookSignature(req, notification)) {
      console.warn('Invalid webhook signature');
      return res.status(401).json({ error: 'Unauthorized' });
    }
    
    // Check for duplicate notifications
    if (await isDuplicateNotification(notification.id, notification.transactionId)) {
      console.log('Duplicate notification ignored');
      return res.status(200).send('OK');
    }
    
    // Process notification
    await processPaymentNotification(notification);
    
    // Log successful processing
    const processingTime = Date.now() - startTime;
    console.log(`Webhook processed successfully in ${processingTime}ms`);
    
    res.status(200).send('OK');
    
  } catch (error) {
    console.error('Webhook processing failed:', error);
    
    // Log error details for debugging
    await logWebhookError(req, error);
    
    // Return 200 to prevent webhook retries for non-recoverable errors
    res.status(200).send('OK');
  }
});

function parseNotificationData(body) {
  // Parse URL-encoded data
  const params = new URLSearchParams(body);
  
  return {
    id: params.get('id'),
    transactionId: params.get('transactionId'),
    transactionStatusId: parseInt(params.get('transactionStatusId')),
    paymentRequestStatusId: parseInt(params.get('paymentRequestStatusId')),
    merchantId: params.get('merchantId'),
    unit: params.get('unit'),
    grossAmount: parseFloat(params.get('grossAmount')),
    fee: parseFloat(params.get('fee')),
    netAmount: parseFloat(params.get('netAmount')),
    referenceId: params.get('referenceId'),
    notes: params.get('notes'),
    clientId: params.get('clientId'),
    clientName: params.get('clientName'),
    clientEmail: params.get('clientEmail'),
    clientPhone: params.get('clientPhone'),
    clientMemberId: params.get('clientMemberId'),
    message: params.get('message'),
    code: params.get('code')
  };
}

Notification Verification

Signature Verification

Verify notification authenticity using HMAC SHA512 signature:
function verifyNotificationSignature(req) {
  const signature = req.headers['x-signature'];
  const payload = req.body;
  const apiKey = process.env.YONOBI_API_KEY;
  
  if (!signature || !payload || !apiKey) {
    return false;
  }
  
  const hash = crypto
    .createHmac('sha512', apiKey)
    .update(payload)
    .digest('base64');
  
  return hash === signature;
}

Verification Process

  1. Header Check: Check X-Signature header in notification request
  2. Algorithm: HMAC SHA512 hash in Base64 format
  3. Key: Use your API key from the control panel
// Complete signature verification example
function verifyWebhookSignature(req, notification) {
  const receivedSignature = req.headers['x-signature'];
  
  if (!receivedSignature) {
    console.warn('Missing X-Signature header');
    return false;
  }
  
  const payload = req.rawBody || req.body;
  const apiKey = process.env.YONOBI_API_KEY;
  
  const expectedSignature = crypto
    .createHmac('sha512', apiKey)
    .update(payload, 'utf8')
    .digest('base64');
  
  // Use timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(receivedSignature),
    Buffer.from(expectedSignature)
  );
}

Status Processing

Payment Status Mapping

function mapPaymentStatus(transactionStatusId, paymentRequestStatusId) {
  // Transaction Status: 0 – waiting, 1 – approved, 2 – declined, 3 – pending
  // Payment Request Status: 1 – paid, 2 – unpaid, 3 – cancelled
  
  if (transactionStatusId === 1 && paymentRequestStatusId === 1) {
    return 'completed';
  } else if (transactionStatusId === 2) {
    return 'failed';
  } else if (transactionStatusId === 3) {
    return 'pending';
  } else if (paymentRequestStatusId === 3) {
    return 'cancelled';
  } else {
    return 'unknown';
  }
}

Business Logic Processing

async function processPaymentNotification(notification) {
  const status = mapPaymentStatus(
    notification.transactionStatusId,
    notification.paymentRequestStatusId
  );
  
  // Update payment record
  await updatePaymentRecord(notification.referenceId, {
    transactionId: notification.transactionId,
    status: status,
    grossAmount: notification.grossAmount,
    fee: notification.fee,
    netAmount: notification.netAmount,
    updatedAt: new Date()
  });
  
  // Execute business logic based on status
  switch (status) {
    case 'completed':
      await handlePaymentSuccess(notification);
      break;
      
    case 'failed':
      await handlePaymentFailure(notification);
      break;
      
    case 'pending':
      await handlePaymentPending(notification);
      break;
      
    case 'cancelled':
      await handlePaymentCancelled(notification);
      break;
  }
  
  // Send internal notifications
  await sendInternalNotification(notification, status);
}

async function handlePaymentSuccess(notification) {
  // Fulfill order
  await fulfillOrder(notification.referenceId);
  
  // Send confirmation email
  await sendPaymentConfirmation(notification.clientEmail, {
    amount: notification.netAmount,
    currency: notification.unit,
    transactionId: notification.transactionId
  });
  
  // Update inventory
  await updateInventory(notification.referenceId);
}

async function handlePaymentFailure(notification) {
  // Cancel order
  await cancelOrder(notification.referenceId);
  
  // Send failure notification
  await sendPaymentFailureNotification(notification.clientEmail, {
    reason: notification.message,
    code: notification.code
  });
  
  // Release inventory
  await releaseInventory(notification.referenceId);
}

Error Handling

Duplicate Prevention

async function isDuplicateNotification(paymentId, transactionId) {
  const key = `webhook_${paymentId}_${transactionId}`;
  
  // Check if we've already processed this notification
  const exists = await redis.exists(key);
  
  if (exists) {
    return true;
  }
  
  // Mark as processed (expire after 24 hours)
  await redis.setex(key, 86400, '1');
  return false;
}

Retry Logic

async function processWithRetry(notification, maxRetries = 3) {
  let attempt = 0;
  
  while (attempt < maxRetries) {
    try {
      await processPaymentNotification(notification);
      return; // Success
      
    } catch (error) {
      attempt++;
      
      if (attempt >= maxRetries) {
        // Final attempt failed, log and alert
        await logCriticalError(notification, error);
        await sendAlertToAdmins(notification, error);
        throw error;
      }
      
      // Wait before retry (exponential backoff)
      await delay(Math.pow(2, attempt) * 1000);
    }
  }
}

Security Best Practices

Webhook Security

  1. Signature Verification: Always verify webhook signatures
  2. HTTPS Only: Use HTTPS endpoints for webhook URLs
  3. IP Whitelisting: Restrict webhook sources to known IPs
  4. Rate Limiting: Implement rate limiting on webhook endpoints

Data Protection

  1. Sensitive Data: Never log sensitive payment information
  2. PCI Compliance: Follow PCI DSS guidelines for payment data
  3. Access Control: Restrict access to webhook processing systems
  4. Audit Logging: Maintain audit logs of all webhook processing

Error Handling

  1. Graceful Failures: Handle errors gracefully without exposing system details
  2. Monitoring: Monitor webhook processing success rates
  3. Alerting: Set up alerts for webhook processing failures
  4. Backup Processing: Implement backup processing for failed webhooks

Testing and Validation

Webhook Testing

// Test webhook endpoint
async function testWebhookEndpoint() {
  const testNotification = {
    id: 'test_12345',
    transactionId: 'test_txn_67890',
    transactionStatusId: 1,
    paymentRequestStatusId: 1,
    merchantId: 'test_merchant',
    unit: 'USD',
    grossAmount: 10.00,
    fee: 0.50,
    netAmount: 9.50,
    referenceId: 'test_order_123'
  };
  
  // Send test notification
  const response = await fetch('/webhooks/payment-notification', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'X-Signature': generateTestSignature(testNotification)
    },
    body: new URLSearchParams(testNotification).toString()
  });
  
  console.log('Test webhook response:', response.status);
}

Best Practices

Implementation

  1. Idempotency: Handle duplicate notifications gracefully
  2. Fast Response: Respond quickly to webhook requests (< 30 seconds)
  3. Async Processing: Process notifications asynchronously when possible
  4. Status Codes: Return appropriate HTTP status codes

Monitoring

  1. Success Rates: Monitor webhook processing success rates
  2. Response Times: Track webhook processing response times
  3. Error Patterns: Analyze error patterns and common failures
  4. Alert Thresholds: Set up appropriate alerting thresholds

Reliability

  1. Retry Mechanisms: Implement retry logic for failed processing
  2. Dead Letter Queue: Use dead letter queues for failed notifications
  3. Backup Processing: Implement backup processing mechanisms
  4. Health Checks: Regular health checks on webhook endpoints

Next Steps

Request Structure

Learn about H2H API request format and parameters

Response Structure

Understand H2H API response handling

Payment Methods

Explore available payment methods and implementations