Documentation Index Fetch the complete documentation index at: https://mintlify.com/own-pay/OwnPay-Documentation/llms.txt
Use this file to discover all available pages before exploring further.
Hooks are the primary integration surface of OwnPay’s plugin system. They let your plugin modify data flowing through the core (filter hooks) and react to events that happen inside it (action hooks) without changing any core file. Every hook in OwnPay is fired by EventManager, which handles error isolation, brand-context scoping, and priority ordering.
Hook Types
Action Hooks Fire-and-forget. The core fires an action at a defined point; any listener registered on that hook runs. The return value is discarded. Use actions to send notifications, write logs, update external systems, or enqueue background jobs.
Filter Hooks Pipeline mutation. Each listener receives the current value, may transform it, and must return it. After all listeners run, the final value is returned to the caller. Use filters to modify data before it is saved or sent, override templates, or augment context arrays.
Registration API
All hook interaction happens through the EventManager instance injected into PluginInterface::register(). Never instantiate EventManager directly.
addAction() — Register an Action Hook
$events -> addAction ( string $hook , callable $callback , int $priority = 10 ): void
public function register ( EventManager $events , Container $container ) : void
{
$events -> addAction ( 'payment.transaction.completed' , [ $this , 'onPaymentCompleted' ], priority : 10 );
}
public function onPaymentCompleted ( array $txn ) : void
{
// $txn contains all columns from op_transactions
// Send SMS, dispatch webhook, etc.
}
addFilter() — Register a Filter Hook
$events -> addFilter ( string $hook , callable $callback , int $priority = 10 ): void
The first argument to the callback is always the value being filtered. The callback must return a value of the same type it received.
$events -> addFilter ( 'payment.fee.calculate' , function ( string $fee , array $context ) : string {
// Waive the fee for transactions over 50,000 BDT
if ( bccomp ( $context [ 'amount' ] ?? '0' , '50000.00' , 2 ) >= 0 ) {
return '0.00' ;
}
return $fee ;
}, priority : 5 ); // lower priority number = runs earlier
removeAction() / removeFilter() — Deregister a Callback
$removed = $events -> removeAction ( 'payment.transaction.completed' , [ $this , 'onPaymentCompleted' ]);
$removed = $events -> removeFilter ( 'payment.fee.calculate' , $myCallback );
// Returns true if found and removed
Inspection and Debugging
// Check if any listener is registered
$events -> hasAction ( 'payment.transaction.completed' ); // bool
$events -> hasFilter ( 'payment.fee.calculate' ); // bool
$events -> hasHook ( 'checkout.head' ); // bool (action or filter)
// Count how many times a hook has fired this request
$events -> getFireCount ( 'payment.transaction.completed' ); // int
// Identify which plugin is currently executing
$events -> getActiveOwner (); // string — plugin slug or 'core'
// Get all registered hooks and listener counts
$events -> getRegisteredHooks ();
// ['payment.transaction.completed' => ['actions' => 3, 'filters' => 0], ...]
// Inspect listeners on a specific hook
$events -> inspectHook ( 'payment.transaction.completed' );
// [['callable' => ..., 'priority' => 10, 'owner' => 'my-plugin'], ...]
Priority and Ordering
Listeners run in ascending priority order. A listener with priority 5 runs before one with priority 10. Multiple listeners at the same priority run in registration order.
// This runs first (priority 5)
$events -> addFilter ( 'payment.amount.calculate' , $earlyModifier , priority : 5 );
// This runs second (priority 10, the default)
$events -> addFilter ( 'payment.amount.calculate' , $defaultModifier );
// This runs last (priority 20)
$events -> addFilter ( 'payment.amount.calculate' , $lateAudit , priority : 20 );
All hook registrations must happen inside register(), not boot() or the constructor. Registrations outside register() are not attributed to your plugin slug and bypass brand-context activation checks — meaning they may fire even when your plugin is disabled.
Execution Model and Error Isolation
Every listener invocation is wrapped in a try/catch(Throwable). If a plugin listener throws any exception, EventManager:
Logs the error: [OwnPay] Hook error in "{hook}" (owner: {slug}): {message} in {file}:{line}.
Continues to the next listener without crashing.
A plugin failure will never crash a critical transaction flow.
Exception : If a listener modifies the db.query.before filter and the resulting SQL fails PluginSandbox::validateSql(), a RuntimeException is re-thrown and halts the request. This is intentional — a plugin attempting to access core tables must be stopped.
Hook Catalogue
The following is the complete hook registry from config/hooks.php. Hooks are verified against every call site in the codebase.
System Lifecycle
Hook Type Source Description system.bootAction KernelFired on every request after the DI container boots, before middleware. system.shutdownAction KernelFired after the response is transmitted. Flush caches, close cursors. system.requestFilter KernelFilter the inbound Request object. Return a Request. Rewrite paths or enforce rate limits. system.responseFilter KernelFilter the outbound Response before it is sent. Parameters: Response, Request. system.middleware.pipelineFilter KernelFilter the global middleware class array. Append custom firewall middleware. system.route.matchedAction RouterFired when a route is resolved. system.routes.registerAction RouterFired when all routes are registered. Add dynamic routes here. system.cron.beforeAction CronJobRunnerFired before cron execution starts. system.cron.afterAction CronJobRunnerFired after cron execution completes.
Database
Hook Type Description db.query.beforeFilter Filter ['sql' => string, 'params' => array] before PDO execution. Security : plugin-modified SQL is validated by PluginSandbox::validateSql(). Blocked queries throw a re-thrown RuntimeException. db.query.afterAction Fired after a query executes. Useful for query logging.
Authentication & Session
Hook Type Parameters Description auth.login.beforeFilter bool $allowed, string $email, string $ipReturn false to block the login before credentials are validated. auth.login.attemptAction string $email, string $ipFired when a login is attempted. auth.login.successAction array $user, string $ipFired on successful login. Dispatch security notifications or reset brute-force counters. auth.login.failedAction string $email, string $ipFired on credential mismatch. Trigger account lockout logic. auth.logoutAction int $userIdFired when a session is terminated. auth.forgot_passwordAction — Fired when a password reset is requested.
Example — Block a login from a specific IP:
$events -> addFilter ( 'auth.login.before' , function ( bool $allowed , string $email , string $ip ) : bool {
$blocklist = [ '192.168.1.100' , '10.0.0.55' ];
if ( in_array ( $ip , $blocklist , true )) {
return false ;
}
return $allowed ;
});
Payment & Transactions
Hook Type Parameters Description payment.transaction.before_createFilter array $data, int $merchantIdModify column map before op_transactions insert. payment.transaction.createdAction array $transactionFired when a transaction enters pending status. payment.transaction.completedAction array $transactionFired on successful payment. Primary hook for SMS, email, webhooks, order fulfilment. payment.transaction.failedAction array $transactionFired on decline or timeout. payment.transaction.cancelledAction array $transactionFired when the buyer cancels checkout. payment.amount.calculateFilter string $amount, array $contextFilter currency and rounding before processor payload. Must return a BCMath-safe string — never cast to float. payment.fee.calculateFilter string $fee, array $contextApply custom tiered fee rules. Context includes amount, currency, merchant_id, gateway_slug. payment.intent.createdAction array $intentFired when a checkout intent session is generated. payment.intent.expiredAction array $intentFired when checkout intents time out. refund.createdAction — Fired when a refund is initiated. transaction.status.changedAction — Fired when a transaction status changes. ledger.entry.createdAction array $entryFired after a balanced double-entry journal is committed. Do not post additional ledger entries from inside this hook. dispute.openedAction array $disputeFired when a chargeback registers. Place merchant balance on hold. dispute.resolvedAction array $disputeFired when dispute resolves. Release or debit funds. payment.refund.reconciliation_failedAction — Fired when a refund reconciliation job encounters an error.
Example — Add a fee surcharge:
$events -> addFilter ( 'payment.amount.calculate' , function ( string $amount , array $context ) : string {
// Add a $1.00 surcharge to all transactions
return bcadd ( $amount , '1.00' , 2 );
}, priority : 10 );
Gateway & Checkout
Hook Type Parameters Description gateway.capture.beforeFilter array $params, string $gatewaySlug, int $merchantIdFilter transaction params sent to gateway adapters. Return shape must preserve amount, currency, trx_id, redirect_url, cancel_url. gateway.capture.afterAction string $slug, array $resultFired after initiate(). Log the payment attempt. gateway.webhook.{slug}Action WebhookPayload $payloadDynamic hook fired after webhook signature verification. {slug} is the gateway slug (e.g., gateway.webhook.stripe). gateway.manual.renderFilter string $html, array $gatewayFilter manual payment gateway HTML markup. gateway.manual.verifyFilter array $result, array $gateway, array $submittedDataFilter manual slip verification results. checkout.beforeAction array $transactionFired before checkout template is parsed. checkout.renderFilter array $dataFilter Twig context before checkout renders. checkout.templateFilter string $templatePathOverride the checkout template path. checkout.status.templateFilter string $templatePathOverride the result page template path. checkout.payment_link.templateFilter string $templatePathOverride the payment link page template path. checkout.intent.renderFilter array $dataFilter variables before payment intent screen. checkout.gateway.selectedAction array $transaction, string $gatewayFired when the buyer selects a gateway. checkout.cancelledAction — Fired when checkout is cancelled. checkout.manual_verify.submittedAction array $transaction, array $proofFired when a manual receipt slip is submitted. checkout.headAction None Inject CSS/fonts into the checkout <head>. checkout.footerAction None Inject JS/tracking scripts into checkout footer. checkout.csp.sourcesFilter array $sourcesAppend gateway domains to CSP. Keys: script_src, style_src, frame_src, connect_src, font_src, img_src.
Admin Panel
Hook Type Description admin.headAction Inject CSS/JS into the admin <head>. admin.footerAction Inject scripts into the admin </body>. admin.menu.registerAction Echo HTML link tags to inject sidebar navigation entries. admin.dashboard.beforeAction Fired before the dashboard body renders. admin.dashboard.bottomAction Fired at the bottom of the dashboard. Inject widget HTML. admin.dashboard.statsFilter array $stats — Inject custom metrics into dashboard stat cards.admin.page.before_renderFilter array $data, string $templatePath — Augment Twig context before any admin page renders.admin.page.after_renderFilter string $html, string $templatePath — Modify final rendered HTML before transmission.admin.template.resolveFilter string $template, array $data — Override admin templates (advanced).admin.template.dataFilter array $data, string $template — Filter admin page context variables.admin.settings.tabsAction Echo an HTML <li> tab trigger to add a custom settings tab. admin.settings.generalAction Inject form fields into the General settings tab. admin.settings.brandingAction Inject form fields into the Branding tab. admin.settings.paymentAction Inject form fields into the Payment tab. admin.settings.emailAction Inject form fields into the Email tab. settings.savedAction Fired when settings are saved. Clear plugin caches here.
Example — Inject a dashboard metric:
$events -> addFilter ( 'admin.dashboard.stats' , function ( array $stats ) : array {
$stats [ 'emails_sent_today' ] = $this -> countEmailsToday ();
return $stats ;
});
Communication & Messaging
Hook Type Parameters Description communication.sms.sendAction int $merchantId, string $recipientNumber, array $deliveryResultFired after outbound SMS delivery. communication.mail.sendAction int $merchantId, array $messagePayload, array $deliveryResultFired after outbound email delivery. communication.channelsFilter array $channelsRegister custom channels (Telegram, Slack, push). Return the modified channels array. communication.template.renderFilter string $html, array $variablesIntercept SMS/email template compilation before delivery. mfs.templatesFilter array $templatesAdd custom regex schemas for MFS SMS parsing.
Customers & Invoices
Hook Type Description customer.createdAction Fired when a customer is created in CustomerPiiService. customer.updatedAction Fired when customer data is updated. customer.deletedAction Fired when a customer is deleted (PII purge). invoice.createdAction Fired when an invoice is created. invoice.updatedAction Fired when an invoice is updated. payment_link.createdAction Fired when a payment link is created. payment_link.updatedAction Fired when a payment link is updated.
Mobile & SMS
Hook Type Description mobile.device.pairedAction Fired when a mobile app pairs with a brand. mobile.device.revokedAction Fired when a device’s API access is revoked. sms.received.beforeAction Fired when an inbound SMS arrives. sms.received.afterAction Fired after inbound SMS processing completes. mobile.sms.matchedAction Fired when an SMS matches a transaction pattern.
Webhook Delivery
Hook Type Description webhook.delivery.successAction Fired when an outbound merchant IPN completes successfully. webhook.delivery.failedAction Fired when outbound IPN delivery fails or reaches retry limits. domain.mappedAction Fired when a white-label custom domain is linked. domain.verifiedAction Fired when a custom domain passes DNS verification. domain.removedAction Fired when a custom domain mapping is deleted. domain.resolveFilter Filter resolved domain mapping details.
Reporting & Export
Hook Type Parameters Description report.dataFilter array $reportData, array $queryParamsFilter financial grid data before rendering. export.rowFilter array $rowTransform or redact individual CSV/export rows. audit.log.createdAction array $entryFired when an admin audit log entry is created.
Plugin Lifecycle
Hook Type Parameters Description plugin.before_installAction string $zipPathFired before a plugin ZIP is extracted. plugin.installedAction string $slug, array $manifestFired after successful installation. plugin.before_activateAction string $slug, int $brandIdFired before activation and migrations. plugin.activatedAction string $slug, int $migrationsCount, int $brandIdFired after activation completes. plugin.before_deactivateAction string $slug, int $brandIdFired before the plugin is suspended. plugin.deactivatedAction string $slug, int $brandIdFlush caches here. Do not drop tables. plugin.before_uninstallAction string $slugFired before plugin files are deleted. plugin.uninstalledAction string $slugFired after deletion and settings purge. plugin.settings.savedAction — Fired when plugin settings are saved. Clear caches. plugins.before_loadAction — Fired before plugin loading begins. plugins.after_loadAction — Fired after all plugins have loaded.
Auto-Updater
Hook Type Description update.availableAction Fired when a newer OwnPay core version is detected. update.beforeAction Fired before a platform update starts. Trigger data backups. update.afterAction Fired after a successful update. update.failedAction Fired when an update execution exception occurs. update.rollbackAction Fired when the system rolls back to a restoration point.
Complete Working Examples
Sending an Outbound HTTP Request on Payment
public function register ( EventManager $events , Container $container ) : void
{
$events -> addAction ( 'payment.transaction.completed' , function ( array $txn ) use ( $container ) : void {
$settings = $container -> get ( \OwnPay\Repository\ SettingsRepository :: class );
$webhookUrl = $settings -> get ( 'plugin.my-plugin' , 'webhook_url' ) ?? '' ;
if ( $webhookUrl === '' ) {
return ;
}
$payload = json_encode ([
'event' => 'payment.completed' ,
'trx_id' => $txn [ 'trx_id' ] ?? '' ,
'amount' => $txn [ 'amount' ] ?? '' ,
'currency' => $txn [ 'currency' ] ?? '' ,
]);
$ch = curl_init ( $webhookUrl );
curl_setopt_array ( $ch , [
CURLOPT_POST => true ,
CURLOPT_RETURNTRANSFER => true ,
CURLOPT_TIMEOUT => 5 ,
CURLOPT_HTTPHEADER => [ 'Content-Type: application/json' ],
CURLOPT_POSTFIELDS => $payload ,
]);
curl_exec ( $ch );
curl_close ( $ch );
}, priority : 15 );
}
Adding a Custom Settings Tab
$events -> addAction ( 'admin.settings.tabs' , function () : void {
echo '<li><button data-tab="my-plugin-tab">My Plugin</button></li>' ;
});
Hook Usage Guidelines
Rule Detail Register in register() only Hooks registered in boot() bypass brand-context activation checks. BCMath for financial values payment.amount.calculate and payment.fee.calculate filters must use bcadd()/bcmul()/bcdiv(). Never cast to float.Keep filters pure Filter callbacks should take input and return output. Avoid mutating $this state. Respect filter return type Returning null from a string filter corrupts downstream logic. Escape echoed HTML Action hooks that echo must escape user-sourced values with htmlspecialchars($val, ENT_QUOTES, 'UTF-8'). Never post to the ledger from hooks Do not call LedgerService::postEntries() from inside a hook callback.