Skip to main content
When a payment fails on the terminal (e.g., card declined, transaction timeout, insufficient funds, or other terminal errors), HitPay will send a failed webhook to your webhook endpoint. This webhook indicates that the payment request has failed and requires proper handling in your application.

Failed Webhook Behavior

When a terminal action fails:
  1. Webhook Triggered: HitPay automatically sends a webhook to your configured webhook URL with status=failed
  2. Payment Request Status Updated: The payment request status is automatically updated to failed in HitPay’s system
  3. Error Information: The webhook includes error details that should be presented to the user

Failed Webhook Payload

When a payment fails on the terminal, HitPay will send a webhook in one of two formats depending on your webhook configuration:

Format 1: Form-Encoded (Webhook v1)

If you’re using the webhook parameter in the Payment Request API (webhook v1), you’ll receive a form-encoded payload with status=failed and includes an error message:
payment_id=&payment_request_id=92965a20-dae5-4d89-a452-5fdfa382dbe1&reference_number=ABC123&phone=&amount=599.00&currency=SGD&status=failed&error_message=Card declined&hmac=330c34a6a8fb9ddb75833620dedb94bf4d4c2e51399d346cbc2b08c381a1399c

Format 2: JSON Event (Webhook Events)

If you’re using registered webhook events (as described in Webhooks documentation), you’ll receive a JSON payload with the payment_request.failed event:
{
  "id": "a03e3915-5ec0-44de-a02b-0af213b62b35",
  "name": "Isabel Witting",
  "email": "hitpaydemotest@yopmail.com",
  "phone": "+6565362368",
  "amount": "7.65",
  "currency": "SGD",
  "is_currency_editable": false,
  "status": "failed",
  "purpose": "Purpose_1747900502",
  "reference_number": "1747900502",
  "payment_methods": [
    "wifi_card_reader"
  ],
  "url": "https://securecheckout.staging.hit-pay.com/payment-request/@nit/a03e3915-5ec0-44de-a02b-0af213b62b35/checkout",
  "redirect_url": "https://google.com",
  "webhook": "https://webhook.site/dab6a317-9acb-4939-b907-4924b5e144ec",
  "send_sms": false,
  "send_email": true,
  "sms_status": "pending",
  "email_status": "pending",
  "allow_repeated_payments": false,
  "expiry_date": null,
  "address": {
    "city": "Gotham",
    "line1": "a",
    "state": "N/A",
    "country": "SG",
    "postal_code": "77777"
  },
  "line_items": null,
  "executor_id": null,
  "created_at": "2025-10-31T14:11:01",
  "updated_at": "2025-10-31T14:11:13",
  "staff_id": null,
  "business_location_id": null,
  "location": null,
  "payments": [
    {
      "id": "a03e3915-eae4-4a0f-9357-6d7869f80ea2",
      "quantity": 1,
      "status": "failed",
      "status_reason_code": "withdrawal_count_limit_exceeded",
      "status_reason": "Withdrawal or limit exceeded. Please use another card.",
      "buyer_name": "Isabel Witting",
      "buyer_phone": "+6565362368",
      "buyer_email": "hitpaydemotest@yopmail.com",
      "currency": "sgd",
      "amount": "7.65",
      "refunded_amount": "0.00",
      "payment_type": "card_present",
      "fees": "0.24",
      "fees_currency": null,
      "created_at": "2025-10-31T14:11:01",
      "updated_at": "2025-10-31T14:11:12"
    }
  ]
}
Key Fields in JSON Event Format:
  • status: Will be "failed" for failed payment requests
  • payments[].status: Will be "failed" for failed payments
  • payments[].status_reason: Human-readable error message explaining the failure
  • payments[].status_reason_code: Machine-readable error code (e.g., "withdrawal_count_limit_exceeded")
Webhook Headers for JSON Events: When receiving JSON event webhooks, check these headers:
  • Hitpay-Event-Type: Will be "failed" for failed events
  • Hitpay-Event-Object: Will be "payment_request" for payment request events
  • Hitpay-Signature: SHA-256 HMAC signature for validation (see Webhook Validation)

Implementation Guidelines

To properly handle failed payment requests in your application, you need to handle both webhook formats:

Handling Form-Encoded Webhooks (Webhook v1)

// In your webhook handler
$payload = $_POST; // Form-encoded payload

if ($payload['status'] === 'failed') {
    // Handle failed payment
    $errorMessage = $payload['error_message'] ?? 'Payment failed';
    $paymentRequestId = $payload['payment_request_id'];
    
    // Update your order/payment status
    updateOrderStatus($paymentRequestId, 'failed');
    
    // Notify your client/UI about the failure
    notifyClientFailure($paymentRequestId, $errorMessage);
    
    // Display error to user
    // You should show the error_message to the user
}

Handling JSON Event Webhooks

// In your webhook handler for JSON events
$headers = getallheaders();
$eventType = $headers['Hitpay-Event-Type'] ?? '';
$eventObject = $headers['Hitpay-Event-Object'] ?? '';
$payload = json_decode(file_get_contents('php://input'), true);

// Check if this is a payment_request.failed event
if ($eventObject === 'payment_request' && $eventType === 'failed') {
    // Extract error message from payments array
    $errorMessage = 'Payment failed';
    if (!empty($payload['payments']) && is_array($payload['payments'])) {
        $failedPayment = $payload['payments'][0];
        $errorMessage = $failedPayment['status_reason'] ?? $failedPayment['status_reason_code'] ?? 'Payment failed';
    }
    
    $paymentRequestId = $payload['id'];
    
    // Update your order/payment status
    updateOrderStatus($paymentRequestId, 'failed');
    
    // Notify your client/UI about the failure
    notifyClientFailure($paymentRequestId, $errorMessage);
    
    // Display error to user
    // You should show the status_reason to the user
}

Displaying Error Messages

Always present the error message from the webhook to your users. This helps them understand why the payment failed:
  • For Form-Encoded Webhooks: Use the error_message field
  • For JSON Event Webhooks: Use the payments[].status_reason field (or status_reason_code as fallback)
Common error messages you may encounter:
  • Card Declined: “Your card was declined. Please try a different payment method.”
  • Insufficient Funds: “Insufficient funds. Please check your account balance.”
  • Timeout: “Payment processing timed out. Please try again.”
  • Terminal Error: “Terminal error occurred. Please contact staff.”
  • Limit Exceeded: “Withdrawal or limit exceeded. Please use another card.”

Best Practices

  1. Update Payment Request Status: Mark the payment request as failed in your system when you receive the failed webhook.
  2. User Experience:
    • Display a clear error message to the user on your POS/kiosk interface
    • Optionally allow the user to retry the payment
    • Update any pending transaction indicators
  3. Security: Always validate webhooks using HMAC verification before processing failed status updates. Never trust unverified webhook payloads.

Common Failure Scenarios

Failed webhooks can be triggered by various terminal errors:
  • Card Declined: Customer’s card was declined by the bank
  • Insufficient Funds: Customer’s account doesn’t have enough balance
  • Transaction Timeout: Payment took too long to process
  • Card Reader Error: Physical issue with the card reader
  • Network Issues: Terminal lost connection during payment
  • Cancelled by User: Customer cancelled the payment on the terminal
  • Invalid Card: Card details are invalid or expired

Important Notes

Always validate webhooks using HMAC verification before processing failed status updates. Never trust unverified webhook payloads.
The failed webhook is sent automatically by HitPay whenever a terminal action fails. You do not need to poll the API for payment status - simply listen to the webhook and handle the response accordingly.