Skip to main content
POST /webhooks/paymentNotify This is an inbound webhook that Rebell sends to your server when a payment status changes. Configure your webhook URL in the paymentNotifyUrl parameter when creating payments.
This is an inbound webhook - Rebell calls your server to notify you of payment events.

Webhook Payload

paymentId
string
required
Rebell payment identifier
paymentRequestId
string
required
Merchant payment request ID (your idempotency key)
paymentStatus
string
required
Payment status:
  • SUCCESS - Payment completed successfully
  • FAIL - Payment failed
paymentAmount
object
required
Payment amount details
paymentTime
string
required
Payment completion time (ISO 8601)
paymentCreatedTime
string
Payment creation time (ISO 8601)

Expected Response

Return a success response to acknowledge receipt:
{
  "result": {
    "resultCode": "SUCCESS",
    "resultStatus": "S",
    "resultMessage": "success"
  }
}
If your webhook fails to respond with a success status, Rebell will retry the notification. Ensure your handler is idempotent to handle duplicate notifications.

Example Webhook Payload

Successful Payment:
{
  "paymentId": "2024011012345678901234",
  "paymentRequestId": "RETAIL-20240110-001",
  "paymentStatus": "SUCCESS",
  "paymentAmount": {
    "currency": "EUR",
    "value": "2500"
  },
  "paymentTime": "2024-01-10T14:30:45+01:00",
  "paymentCreatedTime": "2024-01-10T14:30:00+01:00"
}
Failed Payment:
{
  "paymentId": "2024011012345678901234",
  "paymentRequestId": "RETAIL-20240110-001",
  "paymentStatus": "FAIL",
  "paymentAmount": {
    "currency": "EUR",
    "value": "2500"
  },
  "paymentTime": "2024-01-10T14:30:45+01:00"
}

Webhook Handler Implementation

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

app.post('/webhooks/payment', express.json(), async (req, res) => {
  // 1. Verify the signature
  if (!verifyWebhookSignature(req)) {
    console.error('Invalid webhook signature');
    return res.status(401).json({
      result: { resultStatus: 'F', resultCode: 'INVALID_SIGNATURE' }
    });
  }

  const {
    paymentId,
    paymentRequestId,
    paymentStatus,
    paymentAmount,
    paymentTime
  } = req.body;

  // 2. Check for duplicate notification (idempotency)
  const existingRecord = await getPaymentRecord(paymentRequestId);
  if (existingRecord && existingRecord.webhookProcessed) {
    // Already processed, return success
    return res.json({
      result: { resultStatus: 'S', resultCode: 'SUCCESS' }
    });
  }

  try {
    // 3. Process the payment notification
    if (paymentStatus === 'SUCCESS') {
      await handleSuccessfulPayment({
        paymentId,
        paymentRequestId,
        amount: paymentAmount,
        paidAt: paymentTime
      });
    } else if (paymentStatus === 'FAIL') {
      await handleFailedPayment({
        paymentId,
        paymentRequestId
      });
    }

    // 4. Mark webhook as processed
    await markWebhookProcessed(paymentRequestId, paymentId);

    // 5. Return success
    res.json({
      result: {
        resultCode: 'SUCCESS',
        resultStatus: 'S',
        resultMessage: 'success'
      }
    });

  } catch (error) {
    console.error('Webhook processing error:', error);
    // Return failure - Rebell will retry
    res.status(500).json({
      result: { resultStatus: 'F', resultCode: 'PROCESS_ERROR' }
    });
  }
});

async function handleSuccessfulPayment(payment) {
  // Update order status
  await updateOrderStatus(payment.paymentRequestId, 'paid');

  // Trigger fulfillment
  await triggerFulfillment(payment.paymentRequestId);

  // Send confirmation to customer
  await sendPaymentConfirmation(payment);
}

async function handleFailedPayment(payment) {
  // Update order status
  await updateOrderStatus(payment.paymentRequestId, 'payment_failed');

  // Notify customer
  await sendPaymentFailureNotification(payment.paymentRequestId);
}

Signature Verification

Always verify the webhook signature before processing:
function verifyWebhookSignature(req) {
  const signature = req.headers['signature'];
  const clientId = req.headers['client-id'];
  const responseTime = req.headers['response-time'];
  const body = JSON.stringify(req.body);

  // Extract signature value from header
  const signatureMatch = signature.match(/signature=([^,]+)/);
  if (!signatureMatch) return false;
  const signatureValue = signatureMatch[1];

  // Build content to verify
  const method = 'POST';
  const uri = '/webhooks/payment'; // Your webhook path
  const contentToVerify = `${method} ${uri}\n${clientId}.${responseTime}.${body}`;

  // Verify with Rebell's public key
  const verifier = crypto.createVerify('RSA-SHA256');
  verifier.update(contentToVerify);
  return verifier.verify(REBELL_PUBLIC_KEY, signatureValue, 'base64');
}

Best Practices

Important: The webhook is your source of truth for payment status. Don’t rely solely on the initial API response, especially for asynchronous payments.