News · OwnPay

How to write custom post-payment hooks in OwnPay

Learn how to run custom PHP code and dispatch signed webhooks when payments complete on your self-hosted setup.

By Lincoln Voss·August 22, 2026·3 min read
Key points
  • OwnPay uses WordPress-style hooks like payment.completed to run custom logic on payment success.
  • Every transaction creates balanced double-entry ledger records directly on your server.
  • Cryptographically signed webhooks notify remote services without delaying local customer checkout.

The architecture of a self-hosted event trigger

SaaS payment gateways force developer teams to isolate custom logic behind remote HTTP endpoints. When a customer completes checkout on a cloud platform, you wait for a webhook to traverse public networks, manage queue retries, and handle potential timing delays. When you run self-hosted software on your own PHP server, your custom application code sits right next to the core transaction engine. OwnPay exposes this architecture through an event system modeled on WordPress action and filter hooks.

When a transaction finishes processing, OwnPay commits the state to its internal double-entry ledger. Debit entries strictly match credit entries. Right after that database transaction completes, the engine dispatches internal hook listeners. You can attach custom PHP functions directly to this pipeline. This setup allows you to trigger instant order fulfillment, update local inventory tables, or log custom internal records before the customer ever leaves the checkout screen.

Registering a local payment completion hook

Adding event logic does not require modifying core application files. Core modifications make future platform updates difficult and dangerous. Instead, OwnPay uses a hook registration pattern that keeps custom code contained within separate plugin files.

If you have built software for WordPress, the syntax will look entirely familiar. You register an event callback targeting the standard payment.completed event string:

Hook::on('payment.completed', function ($trx) { Notification::send($trx->merchant, $trx); Analytics::track('conversion', $trx->amount); });

The $trx variable passes the full transaction object into your closure. You gain immediate access to customer attributes, line item amounts, currency metadata, and gateway details. Because this code executes within the local PHP process, execution is fast. You avoid the network latency inherent in calling third-party API proxies.

Maintaining audit readiness with double-entry bookkeeping

Payment events frequently require manual or automated adjustments to account totals. A common mistake in custom payment builds is updating transaction amounts through simple database increments. That approach breaks audit trails and makes reconciliation difficult during monthly accounting runs.

OwnPay solves this structural flaw by embedding a balanced double-entry ledger directly into the core framework. Every financial movement generates offsetting debit and credit entries. When you write custom plugins that perform financial adjustments, you interact directly with this ledger interface:

$ledger->record(['debit' => ['customer_receivable', $amount], 'credit' => ['merchant_revenue', $amount]]);

Using strict double-entry records keeps your financial data clean. Debits balance credits down to the penny. If an unexpected error occurs during hook processing, database transactions roll back to prevent half-written financial state.

Dispatching signed webhooks to external services

Local PHP hooks work best for immediate, low-latency tasks on the host server. However, modern stacks often require notifying offsite services like third-party fulfillment centers, external analytics platforms, or microservice queues. For out-of-process communication, OwnPay provides signed webhooks.

When an event triggers, the software formats a clean JSON payload detailing the transaction event:

{"event": "payment.completed", "amount": 4999, "currency": "USD"}

To keep external endpoints secure, every webhook includes a cryptographic HMAC signature in the request headers. Remote services verify this signature against a shared secret before accepting the payload. This step prevents rogue actors from spoofing payment completion events.

If a remote system needs additional context after receiving a payload, it queries the OwnPay REST API directly:

GET /api/v1/payments/pay_7Xk9mN2q

This separation of concerns keeps checkout fast. If an external service experiences slow response times or temporary outages, your customer is not stuck waiting on a hanging checkout page.

Testing custom hooks across multiple gateway integrations

Before deploying payment hooks to live production environments, test your callback code across various gateway types. OwnPay supports more than 123 payment gateway integrations, ranging from global processors like Stripe and Adyen to regional options like bKash and Nagad.

Gateway behavior varies based on settlement mechanisms. Cards processed via Stripe typically finalize synchronously during payment submission. Alternative payment methods or mobile wallets may settle asynchronously via provider-initiated webhooks. Your local payment.completed hook executes regardless of how the gateway reports payment status, but testing both paths confirms your code behaves predictably under every scenario.

Running self-hosted payment software under the AGPL license means you avoid recurring 0% platform cuts and preserve full data ownership. By extending the platform through clean plugin hooks and HMAC-signed webhooks, you build reliable payment workflows entirely on your own terms.

More from OwnPay News
Published via Stork Wire — independent trade coverage, in partnership with this site.