E-commerce•Anushka Dahanayake••Updated Sep 30, 2026

Building Custom Payment Gateway Plugins for WooCommerce: Security, Webhooks, and Error Logging

Custom payment gateways let a store integrate a processor or bank that has no maintained plugin. Learn how to engineer a secure WooCommerce payment plugin from scratch.

Building Custom Payment Gateway Plugins for WooCommerce: Security, Webhooks, and Error Logging article cover image

Most stores should use an official, maintained gateway plugin such as Stripe, PayPal or WooPayments. A custom gateway plugin makes sense when a business must integrate a processor, acquiring bank or regional payment method that has no maintained WooCommerce plugin, or when the checkout flow needs control an off-the-shelf plugin cannot provide.

However, payment processing is a high-risk system. Implementing poor webhook verification or weak hashing signatures exposes your store to checkout exploits, where users can forge transaction success states to download products without actually paying.

This guide provides a secure blueprint for building a custom WooCommerce payment gateway plugin, focusing on webhook authorization, transaction validation, and error logging.


1. Structure of a WooCommerce Gateway Class

Every WooCommerce payment gateway must extend the base WC_Payment_Gateway class. This class registers your gateway settings, handles payment forms, processes the redirect to the bank endpoint, and listens for validation webhooks.

Create your main plugin file (e.g., custom-ipg-gateway.php) and register the custom class during the init phase:

`php

<?php

/*

Plugin Name: WooCommerce Custom IPG Gateway

Description: A secure custom payment integration for WooCommerce.

Version: 1.0.0

Author: Anushka Dahanayake

*/

add_action( 'plugins_loaded', 'init_custom_ipg_gateway_class' );

// Tell WooCommerce the gateway exists.

add_filter( 'woocommerce_payment_gateways', function ( $gateways ) {

$gateways[] = 'WC_Gateway_Custom_IPG';

return $gateways;

} );

function init_custom_ipg_gateway_class() {

// Check inside the hook: WooCommerce may load after this plugin.

if ( ! class_exists( 'WC_Payment_Gateway' ) ) {

return;

}

class WC_Gateway_Custom_IPG extends WC_Payment_Gateway {

public function __construct() {

$this->id = 'custom_ipg';

$this->icon = apply_filters( 'woocommerce_custom_ipg_icon', '' );

$this->has_fields = false;

$this->method_title = __( 'Custom IPG', 'wc-custom-ipg' );

$this->method_description = __( 'Redirects clients to secure local payment gateway.', 'wc-custom-ipg' );

$this->init_form_fields();

$this->init_settings();

$this->title = $this->get_option( 'title' );

$this->description = $this->get_option( 'description' );

$this->merchant_id = $this->get_option( 'merchant_id' );

$this->secret_key = $this->get_option( 'secret_key' );

add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );

add_action( 'woocommerce_api_wc_gateway_custom_ipg', array( $this, 'check_ipn_response' ) );

}

public function init_form_fields() {

$this->form_fields = array(

'enabled' => array(

'title' => __( 'Enable/Disable', 'wc-custom-ipg' ),

'type' => 'checkbox',

'label' => __( 'Enable Custom IPG', 'wc-custom-ipg' ),

'default' => 'no'

),

'title' => array(

'title' => __( 'Title', 'wc-custom-ipg' ),

'type' => 'text',

'default' => __( 'Secure Card Payment', 'wc-custom-ipg' ),

),

'merchant_id' => array(

'title' => __( 'Merchant ID', 'wc-custom-ipg' ),

'type' => 'text',

),

'secret_key' => array(

'title' => __( 'API Secret Key', 'wc-custom-ipg' ),

'type' => 'password',

)

);

}

}

}

`


2. Handling the Checkout Redirect

When the user clicks "Place Order", the process_payment() method is triggered. Instead of handling credit card fields locally (which puts your whole site in scope for the most demanding PCI DSS requirements), your plugin should compile transaction parameters and redirect the user to the provider's hosted payment page. For cards issued in the EU/EEA and UK, that hosted page must handle Strong Customer Authentication under PSD2 (normally 3-D Secure 2), so confirm the provider supports it before you build.

`php

public function process_payment( $order_id ) {

$order = wc_get_order( $order_id );

// Compile redirect query parameters

$payment_url = 'https://pay.example-provider.com/checkout';

$query_args = array(

'merchant' => $this->merchant_id,

'order_id' => $order_id,

'amount' => $order->get_total(),

'currency' => $order->get_currency(),

'return_url' => $this->get_return_url( $order ),

'cancel_url' => $order->get_cancel_order_url(),

// Signature to prevent parameter tampering. Use the exact

// algorithm and field order your provider documents.

'hash' => hash_hmac( 'sha256', $this->merchant_id . $order_id . $order->get_total() . $order->get_currency(), $this->secret_key )

);

return array(

'result' => 'success',

'redirect' => add_query_arg( $query_args, $payment_url )

);

}

`


3. Securing Webhook Listeners (Instant Payment Notifications - IPN)

When a transaction succeeds, the payment gateway sends a POST request back to your server's webhook endpoint (configured via the woocommerce_api_wc_gateway_custom_ipg hook registered in your constructor).

[!WARNING]

Never update order status to "Completed" based on user-facing query redirects. Always require a signed backend-to-backend webhook payload containing validation parameters.

Secure Webhook Verification Endpoint:

`php

public function check_ipn_response() {

$field = function ( $key ) {

return isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';

};

$merchant_id = $field( 'merchant_id' );

$order_id = absint( $field( 'order_id' ) );

$status_code = $field( 'status_code' );

$amount = $field( 'amount' );

$currency = $field( 'currency' );

$hash = $field( 'hash' );

// Step 1: Recreate the signature and compare in constant time

$local_hash = hash_hmac( 'sha256', $merchant_id . $order_id . $amount . $currency . $status_code, $this->secret_key );

if ( $merchant_id !== $this->merchant_id || ! hash_equals( $local_hash, strtolower( $hash ) ) ) {

// Log unauthorized attempt and exit

$this->log_gateway_error( "Hash mismatch. Potential fraud payload from IP: " . $_SERVER['REMOTE_ADDR'] );

status_header( 403 );

exit;

}

$order = wc_get_order( $order_id );

if ( ! $order ) {

$this->log_gateway_error( "Order ID {$order_id} not found." );

status_header( 404 );

exit;

}

// Idempotency: a retried callback for a paid order is a confirmation, not a new payment

if ( $order->is_paid() ) {

status_header( 200 );

exit;

}

// Step 2: Verify amount and currency match the order exactly

if ( wc_format_decimal( $amount, 2 ) !== wc_format_decimal( $order->get_total(), 2 ) || $currency !== $order->get_currency() ) {

$this->log_gateway_error( "Amount mismatch for Order {$order_id}. Pay: {$amount}, Order: " . $order->get_total() );

$order->update_status( 'on-hold', __( 'Payment amount validation mismatch.', 'wc-custom-ipg' ) );

status_header( 400 );

exit;

}

// Step 3: Update Order Status

if ( $status_code === '2' ) { // Success code from the provider's docs

$order->payment_complete( $field( 'transaction_id' ) );

$order->add_order_note( __( 'IPN Validation Success. Card Charged.', 'wc-custom-ipg' ) );

status_header( 200 );

exit;

} else {

$order->update_status( 'failed', __( 'Transaction declined by bank API.', 'wc-custom-ipg' ) );

status_header( 200 );

exit;

}

}

`


4. Professional Error Logging

To troubleshoot payment errors and audit disputed logs, implement a helper logging method using WooCommerce's native WC_Logger class:

`php

private function log_gateway_error( $message ) {

if ( class_exists( 'WC_Logger' ) ) {

$logger = wc_get_logger();

$logger->error( $message, array( 'source' => 'custom-ipg-gateway' ) );

}

}

`

With the default file handler, entries are written under wp-content/uploads/wc-logs/ and can be reviewed under WooCommerce > Status > Logs.

By isolating your webhook endpoints, verifying signatures, validating amounts and maintaining logs, you close the most common ways a forged callback can mark an unpaid order as paid.

Supporting the Block-Based Checkout

The Cart and Checkout blocks are the default for new WooCommerce stores. A gateway built only on WC_Payment_Gateway appears in the classic shortcode checkout but not in the block checkout. To support it, register a payment method integration that extends Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType on the woocommerce_blocks_payment_method_type_registration hook, and ship a small script that calls registerPaymentMethod from @woocommerce/blocks-registry (the wc.wcBlocksRegistry global). The WooCommerce payment method integration docs cover the details. Test both checkout types if the store could use either.

Production Security Checklist

A payment gateway plugin should be treated as financial infrastructure. Before launch, review:

  • Secret keys are stored in protected settings and never printed.
  • Webhook signatures are verified server-side.
  • Order amount, currency and merchant ID are checked.
  • Duplicate webhook events are idempotent.
  • Failed, cancelled, pending and refunded states are handled.
  • User-facing return URLs never mark payment as successful alone.
  • Logs avoid full card, customer or secret data.
  • Admin settings require proper capability checks.
  • Test mode and live mode cannot be confused.
  • Refund and reconciliation workflows are documented.

For any gateway, confirm the exact signature formula from the provider and test with real sandbox callbacks, not only browser redirects.

Operational Testing

Run test orders for successful payment, failed payment, cancelled payment, duplicate webhook, wrong amount, wrong currency, missing order, delayed callback and refund. Reconcile WooCommerce order status with gateway dashboard records and settlement exports.

Payment reliability is not only code correctness. It includes support scripts, finance checks, customer communication and incident handling when a provider is delayed.

Frequently Asked Questions

Should a WooCommerce payment plugin collect card details directly?

Usually no. Redirect or hosted-field approaches reduce PCI scope because the gateway handles sensitive card entry. Direct card handling requires much stricter compliance.

Can an order be completed from the return URL?

No. The return URL is controlled by the user's browser and can be replayed or manipulated. Complete orders only after trusted server-side verification.

What is idempotency in payment webhooks?

It means repeated callbacks for the same transaction do not create duplicate payments, duplicate emails or conflicting order states.

What should payment logs include?

Include order ID, gateway reference, status, validation result and bounded error context. Avoid secrets and sensitive payment data.

Order State Design

Map every gateway response to a WooCommerce order state before writing code. Common states include pending payment, processing, completed, failed, cancelled, refunded and on-hold. Some gateways also return pending review, 3D Secure challenge, bank timeout or settlement delay.

Do not collapse every non-success result into failed. A pending bank transfer, fraud review or delayed callback may need a different operational response. The customer email, staff alert and admin note should explain what happened without exposing sensitive gateway data.

Reconciliation and Support

Payment plugins need reconciliation because providers, stores and analytics can disagree. Store the gateway transaction reference, order ID, amount, currency, status, verification timestamp and callback ID. That allows finance or support to compare WooCommerce with the gateway dashboard.

Create a support workflow for charged-but-not-completed orders, completed-but-not-settled orders, duplicate callbacks and customer disputes. The plugin should make these cases visible instead of hiding them inside vague logs.

Final Recommendation

Build custom payment gateways only when the business need is real and the security process is mature. Hosted payment pages, signed webhooks, idempotent state changes, careful logging and reconciliation are the minimum standard.

100-Point Payment Gateway Readiness Score

Score the plugin before live payments:

AreaPoints
Hosted or compliant payment entry10
Signature verification15
Amount and currency validation15
Idempotent webhook handling10
Complete order-state mapping10
Secure settings storage10
Bounded logs without secrets10
Refund and dispute workflow10
Sandbox and live test orders5
Reconciliation documentation5

Any score below 85 should block launch. Payment bugs create direct financial, legal and customer trust risk.

Maintenance Requirements

After launch, review gateway API changes, WooCommerce updates, PHP version changes, webhook failures, fraud attempts, abandoned checkout reports and support tickets. Payment integrations should be monitored permanently, especially when plugins, themes or checkout settings change.

Example Failure Case

A customer pays successfully at the bank, but the callback reaches WooCommerce late. If the plugin only trusts the browser return page, the order may be completed without proof or marked failed even though money was captured. A mature gateway plugin stores the pending order, waits for a signed webhook, records the gateway reference and gives staff a clear reconciliation path.

Another common case is duplicate callbacks. A gateway may retry the same notification several times. Without idempotency, the store can send duplicate emails, reduce stock twice or create confusing order notes. The plugin should recognize the transaction reference and treat repeated valid callbacks as confirmations, not new payment events.

These details are why payment code must be designed around failure, not only the happy path.

Postlaunch Monitoring Plan

For the first 30 days, review gateway logs daily. Track failed payments, pending orders, webhook failures, duplicate callbacks, order notes, refund attempts and customer support messages. Compare total paid orders in WooCommerce with the gateway dashboard and settlement exports.

After the first month, move to weekly reviews unless transaction volume is high. Keep alerts active for signature failures, amount mismatches and repeated webhook errors. These signals can reveal configuration mistakes, provider outages or attempted fraud.

Build Versus Buy

Use an official gateway plugin when it supports the business flow, currency, settlement process and checkout experience. Build custom only when the official option is missing, unreliable or unable to support a required payment workflow.

Custom code creates long-term responsibility. The business must maintain provider API changes, security updates, WooCommerce compatibility, support workflows and logs. If that ownership is not available, a supported plugin or hosted checkout is usually safer.

For secure implementation, connect this work with WooCommerce Store Setup, Checkout Optimization, and Website Maintenance.

Related posts

Shared vs VPS vs Managed Hosting for a Small Business Website or Store article cover image
Hosting, VPS & DevOps••11 min read

Shared vs VPS vs Managed Hosting for a Small Business Website or Store

A plain comparison of shared hosting, VPS and managed hosting or PaaS for small business sites and online stores: responsibilities, isolation and performance, the signs a WooCommerce store or Next.js app has outgrown shared hosting, GDPR data residency, and a decision table.

Read article →

How to Secure a New Ubuntu VPS: A Setup Checklist for Business Websites article cover image
Hosting, VPS & DevOps••11 min read

How to Secure a New Ubuntu VPS: A Setup Checklist for Business Websites

A step-by-step hardening checklist for a fresh Ubuntu 26.04 or 24.04 LTS VPS that will host a business website, with copy-paste commands for SSH keys, ufw, unattended-upgrades, fail2ban, time sync, swap, monitoring and backups.

Read article →

Deploy a Next.js 16 App on a VPS with Nginx, systemd or PM2, and HTTPS article cover image
Hosting, VPS & DevOps••11 min read

Deploy a Next.js 16 App on a VPS with Nginx, systemd or PM2, and HTTPS

A working guide to running Next.js 16 on your own VPS: Node.js LTS, build-time versus runtime environment variables, a systemd unit and PM2 alternative, an Nginx server block with certbot HTTPS, the standalone output option, logs, and a two-port release script.

Read article →

Author

Anushka Dahanayake

Anushka Dahanayake builds SEO-focused websites, e-commerce platforms, dashboards, and automation systems for businesses worldwide.