RevKeen Docs

Terminal Webhook Events

Subscribe to real-time terminal payment events via webhooks

Terminal payments emit webhook events throughout the payment lifecycle. Subscribe to these events to receive real-time notifications when payments are requested, approved, declined, or reversed.

Event Types

Terminal Payment Events

EventFired When
billing.terminal_payment.requestedPayment command sent to terminal
billing.terminal_payment.succeededTerminal approved the payment
billing.terminal_payment.declinedTerminal declined the payment
billing.terminal_payment.cancelledMerchant cancelled before card presentation
billing.terminal_payment.errorTerminal error or timeout

Terminal Refund and Void Events

EventFired When
billing.terminal_refund.succeededTerminal refund approved
billing.terminal_void.succeededTerminal void approved

Event Payload

All terminal webhook events share a common payload structure:

billing.terminal_payment.succeeded

{
  "id": "evt_xxxxxxxx",
  "type": "billing.terminal_payment.succeeded",
  "created": "2026-03-11T10:00:15Z",
  "data": {
    "terminal_payment_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "invoice_id": "inv_xxxxxxxx",
    "device_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
    "amount_minor": 5000,
    "currency": "GBP",
    "terminal_serial": "21032100001",
    "auth_code": "123456",
    "card_scheme": "VISA",
    "masked_pan": "************1234",
    "entry_mode": "contactless"
  }
}

billing.terminal_payment.declined

{
  "id": "evt_xxxxxxxx",
  "type": "billing.terminal_payment.declined",
  "created": "2026-03-11T10:01:00Z",
  "data": {
    "terminal_payment_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "invoice_id": "inv_xxxxxxxx",
    "device_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
    "amount_minor": 5000,
    "currency": "GBP",
    "terminal_serial": "21032100001",
    "error_message": "Insufficient funds",
    "error_category": "insufficient_funds"
  }
}

billing.terminal_payment.error

{
  "id": "evt_xxxxxxxx",
  "type": "billing.terminal_payment.error",
  "created": "2026-03-11T10:03:00Z",
  "data": {
    "terminal_payment_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "invoice_id": "inv_xxxxxxxx",
    "device_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
    "amount_minor": 5000,
    "currency": "GBP",
    "terminal_serial": "21032100001",
    "failure_reason": "timeout",
    "error_message": "Terminal did not respond within 3 minutes"
  }
}

billing.terminal_refund.succeeded

{
  "id": "evt_xxxxxxxx",
  "type": "billing.terminal_refund.succeeded",
  "created": "2026-03-11T14:00:00Z",
  "data": {
    "terminal_payment_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "original_payment_id": "orig-payment-uuid",
    "device_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
    "amount_minor": 2500,
    "currency": "GBP",
    "terminal_serial": "21032100001",
    "auth_code": "654321",
    "card_scheme": "VISA",
    "masked_pan": "************1234"
  }
}

Error Event Differentiation

The billing.terminal_payment.error event includes a failure_reason field to distinguish failure modes:

failure_reasonDescription
timeoutTerminal did not respond within 3 minutes
terminal_errorPAX terminal returned an error (hardware issue, comms failure)
connection_lostConnector went offline during the transaction

Webhook Handler Example

app.post('/webhooks/revkeen', async (req, res) => {
  const event = req.body;

  switch (event.type) {
    case 'billing.terminal_payment.succeeded':
      // Payment approved — fulfil the order
      await fulfillOrder(event.data.invoice_id);
      await sendReceipt(event.data.terminal_payment_id);
      break;

    case 'billing.terminal_payment.declined':
      // Decline — notify staff with the reason
      await notifyStaff(
        `Payment declined: ${event.data.error_category}`
      );
      break;

    case 'billing.terminal_payment.error':
      if (event.data.failure_reason === 'timeout') {
        await notifyStaff('Terminal timed out — check device connectivity');
      }
      break;

    case 'billing.terminal_refund.succeeded':
      await updateInventory(event.data.terminal_payment_id);
      break;

    case 'billing.terminal_void.succeeded':
      await updateInventory(event.data.terminal_payment_id);
      break;
  }

  res.json({ received: true });
});

Webhook Signature Verification

Terminal webhook events use the same signature verification as all RevKeen webhooks. See the Webhooks documentation for details on verifying the X-RevKeen-Signature header.

Retry Behavior

If your webhook endpoint returns a non-2xx status code, RevKeen will retry the delivery:

  • Retry schedule: 5 minutes, 30 minutes, 2 hours, 24 hours
  • Maximum retries: 4 attempts after the initial delivery
  • Timeout: 30 seconds per delivery attempt

Common Use Cases

Use CaseEvents to Subscribe
Update order statusbilling.terminal_payment.succeeded
Send digital receiptbilling.terminal_payment.succeeded
Alert on failuresbilling.terminal_payment.error, billing.terminal_payment.declined
Update inventory after returnbilling.terminal_refund.succeeded
Audit trailAll billing.terminal_* events

On this page