Hooks and Filters Reference
WP Sell Services and WP Sell Services Pro fire 454 actions and filters between them.
This page covers the behavioural surface – services, orders, delivery, money, disputes, REST, and the Pro seams – each with its parameters and the source file that fires it.
Template hooks are on their own page. The ~110 wpss_before_* / wpss_after_* markup slots, the dashboard section hooks, and the display filters are documented next to the templates that fire them, in Template Overrides. Use that page when you want to inject markup rather than change behaviour.
Between the two, 377 of the 454 hooks are documented. The rest are internal and may change without notice – if you need one, open a support request and we will promote it rather than have you bind to a moving target.
Verify before you ship. Hook names and signatures on this page are checked against the 1.3.0 source. Some names in pre-1.3.0 documentation were never fired at all – if a callback of yours has silently stopped running, search this page for the hook name before assuming a regression.
Using Hooks
Section titled “Using Hooks”// Actions execute code at specific pointsadd_action( 'wpss_order_status_changed', 'my_func', 10, 3 );function my_func( $order_id, $new_status, $old_status ) { // Your code here}
// Filters modify data before it is usedadd_filter( 'wpss_review_window_days', fn( $days ) => 14 );Plugin Lifecycle Actions
Section titled “Plugin Lifecycle Actions”| Hook | Parameters | File |
|---|---|---|
wpss_loaded |
Plugin $plugin |
src/Core/Plugin.php:303 |
wpss_adapter_initialized |
EcommerceAdapterInterface $adapter |
src/Integrations/IntegrationManager.php:136 |
wpss_loaded is the primary extension hook. All Pro features register here:
add_action( 'wpss_loaded', function( $plugin ) { // Plugin is ready - register extensions}, 10, 1 );Service Actions
Section titled “Service Actions”| Hook | Parameters | File |
|---|---|---|
wpss_service_created |
int $post_id, array $data |
src/Services/ServiceManager.php:188 |
wpss_service_updated |
int $service_id, array $data |
src/Services/ServiceManager.php:305 |
Service Filters
Section titled “Service Filters”| Filter | Parameters | File |
|---|---|---|
wpss_pre_create_service |
array $data |
ServiceManager.php |
wpss_pre_update_service |
array $data, int $service_id |
ServiceManager.php |
wpss_before_service_deleted |
int $service_id |
src/Services/ServiceManager.php:342 |
wpss_service_meta_saved |
int $post_id, WP_Post $post |
src/Admin/Metaboxes/ServiceMetabox.php:913 |
wpss_rest_service_created |
int $service_id, WP_REST_Request $request |
src/API/ServicesController.php:751 |
wpss_rest_service_updated |
int $service_id, WP_REST_Request $request |
src/API/ServicesController.php:916 |
wpss_rest_service_deleted |
int $service_id, bool $force |
src/API/ServicesController.php:965 |
Moderation Actions
Section titled “Moderation Actions”| Hook | Parameters | File |
|---|---|---|
wpss_service_approved |
int $service_id, string $notes |
src/Services/ModerationService.php:196 |
wpss_service_rejected |
int $service_id, string $reason |
src/Services/ModerationService.php:246 |
wpss_service_pending_moderation |
int $service_id |
src/Frontend/ServiceWizard.php:1970 |
Order Actions
Section titled “Order Actions”| Hook | Parameters | File |
|---|---|---|
wpss_order_status_changed |
int $order_id, string $new_status, string $old_status |
src/Admin/Pages/ManualOrderPage.php:743 |
wpss_order_status_{status} |
int $order_id, string $old_status |
OrderService.php:197 |
wpss_order_created |
int $order_id, string $status |
src/functions/orders.php:1196 |
Order Filters
Section titled “Order Filters”| Filter | Parameters | File |
|---|---|---|
wpss_pre_create_order |
array $order_data |
StandaloneOrderProvider.php |
wpss_pre_order_status_change |
bool $allow, int $order_id, string $new_status, string $old_status |
OrderService.php |
wpss_order_started |
int $order_id |
src/API/OrdersController.php:944 |
wpss_order_completed |
int $order_id, object $order |
src/Services/OrderWorkflowManager.php:705 |
wpss_order_cancelled |
int $order_id, int $user_id, string $reason |
src/Services/OrderWorkflowManager.php:804 |
wpss_order_disputed |
int $order_id, int $opened_by, string $reason |
src/API/OrdersController.php:1148 |
wpss_order_message_created |
int $message_id, int $order_id, int $user_id |
src/API/OrdersController.php:685 |
wpss_requirement_field_label |
string $label, string $key |
src/functions/orders.php:356 |
wpss_after_status_change_notification |
int $order_id, string $new_status, string $old_status |
src/Services/OrderWorkflowManager.php:666 |
wpss_send_requirements_reminder_email |
int $order_id, int $reminder_num, string $message |
src/Services/OrderWorkflowManager.php:493 |
wpss_requirements_timeout |
int $order_id, bool $auto_start |
src/Services/OrderWorkflowManager.php:628 |
Removed in 1.4.0:
wpss_order_accepted,wpss_order_rejected,wpss_order_delivered. The first two went when the deadaccept/rejectorder verbs were removed – they never had a real transition behind them.wpss_order_deliveredwent whendeliverwas routed throughDeliveryService, which already fires its own, better-shaped hooks.Rebind as follows:
Was Use instead wpss_order_acceptedwpss_order_paid, orwpss_order_status_changed– an order is accepted by being paidwpss_order_rejectedwpss_order_cancelledwpss_order_deliveredwpss_delivery_submitted($delivery_id, $order_id) orwpss_delivery_accepted($order_id)All three are gone from the source, so a callback still bound to them runs never – silently. Grep your integrations.
Delivery Actions
Section titled “Delivery Actions”Delivery Filters
Section titled “Delivery Filters”| Filter | Parameters | File |
|---|---|---|
wpss_pre_submit_delivery |
array $delivery_data, int $order_id |
DeliveryService.php |
| Hook | Parameters | File |
|---|---|---|
wpss_delivery_submitted |
int $delivery_id, int $order_id |
src/Services/DeliveryService.php:151 |
wpss_delivery_accepted |
int $order_id |
src/Services/DeliveryService.php:206 |
wpss_revision_requested |
int $order_id, string $reason |
src/Services/DeliveryService.php:276 |
wpss_requirements_submitted |
int $order_id, array $field_data, array $attachments |
src/Services/RequirementsService.php:521 |
wpss_cancellation_requested |
int $order_id, int $user_id, string $reason, string $note |
src/Services/OrderService.php:1389 |
wpss_order_auto_refunded |
int $order_id, object $order, mixed $refund_result |
src/Services/OrderWorkflowManager.php:1591 |
wpss_new_order_message |
int $order_id, int $sender_id, string $content |
src/Services/ConversationService.php:351 |
Payment and Gateway Actions
Section titled “Payment and Gateway Actions”These hooks fire during payment processing, gateway interactions, and checkout flow.
Standalone Adapter
Section titled “Standalone Adapter”| Hook | Parameters | File |
|---|---|---|
wpss_standalone_adapter_init |
StandaloneAdapter $adapter |
src/Integrations/Standalone/StandaloneAdapter.php:156 |
wpss_standalone_checkout_processed |
int $order_id, array $order_data |
StandaloneCheckoutProvider.php:134 |
wpss_standalone_order_complete |
object $order |
src/Integrations/Standalone/StandaloneOrderProvider.php:741 |
wpss_order_paid |
int $order_id, string $transaction_id |
src/Integrations/Standalone/StandaloneOrderProvider.php:437 |
wpss_order_status_pending_requirements |
int $order_id, string $old_status |
src/Integrations/Standalone/StandaloneOrderProvider.php:428 |
wpss_payment_callback |
string $gateway_id |
src/Integrations/Standalone/StandaloneAdapter.php:335 |
Offline Gateway
Section titled “Offline Gateway”| Hook | Parameters | File |
|---|---|---|
wpss_offline_multi_orders_created |
array $order_ids, int $customer_id |
src/Integrations/Gateways/OfflineGateway.php:740 |
wpss_offline_order_created |
int $order_id, object $order |
src/Integrations/Gateways/OfflineGateway.php:664 |
wpss_offline_order_paid |
int $order_id, string $transaction_id |
src/Integrations/Gateways/OfflineGateway.php:925 |
Stripe Gateway
Section titled “Stripe Gateway”| Hook | Parameters | File |
|---|---|---|
wpss_stripe_webhook_received |
string $event_type, object $data, string $payload |
src/Integrations/Stripe/StripeGateway.php:660 |
wpss_stripe_refund_processed |
string $payment_intent_id, object $charge |
src/Integrations/Stripe/StripeGateway.php:1623 |
PayPal Gateway
Section titled “PayPal Gateway”| Hook | Parameters | File |
|---|---|---|
wpss_paypal_refund_processed |
string $url, array $resource |
src/Integrations/PayPal/PayPalGateway.php:1054 |
Payment REST API
Section titled “Payment REST API”| Hook | Parameters | File |
|---|---|---|
wpss_rest_offline_order_created |
int $order_id, object $order, string $gateway_id |
src/API/PaymentController.php:444 |
Payment Filters
Section titled “Payment Filters”| Filter | Parameters | File |
|---|---|---|
wpss_stripe_payment_intent_args |
array $params, int $order_id, int $vendor_id |
src/Integrations/Stripe/StripeGateway.php:299 |
wpss_rest_create_payment_intent |
null, object $gateway, float $amount, string $currency, int $service_id, int $package_id, object $pay_order |
src/API/PaymentController.php:270 |
wpss_rest_confirm_payment |
null, object $gateway, string $payment_id, int $service_id, int $package_id, object $pay_order |
src/API/PaymentController.php:319 |
wpss_checkout_tax_rate |
float $tax_rate, int $vendor_id, int $service_id |
src/functions/money.php:831 |
wpss_stripe_payment_intent_args lets you modify Stripe PaymentIntent parameters before creation:
add_filter( 'wpss_stripe_payment_intent_args', function( $params, $order_id, $vendor_id ) { $params['metadata']['custom_field'] = 'value'; return $params;}, 10, 3 );wpss_checkout_tax_rate lets you apply different tax rates per vendor or service:
add_filter( 'wpss_checkout_tax_rate', function( $rate, $vendor_id, $service_id ) { // Apply 15% tax for services in a specific category if ( has_term( 'consulting', 'wpss_service_category', $service_id ) ) { return 15.0; } return $rate;}, 10, 3 );Data Cascade Actions
Section titled “Data Cascade Actions”These hooks fire when services, requests, or users are deleted and related data is cleaned up. Use them for custom cleanup logic.
| Hook | Parameters | File |
|---|---|---|
wpss_before_cascade_delete_service |
int $service_id |
DataCascadeHandler.php:101 |
wpss_after_cascade_delete_service |
int $service_id |
src/Services/DataCascadeHandler.php:135 |
wpss_before_cascade_delete_request |
int $request_id |
src/Services/DataCascadeHandler.php:151 |
wpss_after_cascade_delete_request |
int $request_id |
src/Services/DataCascadeHandler.php:162 |
wpss_before_cascade_delete_user |
int $user_id |
src/Services/DataCascadeHandler.php:178 |
wpss_after_cascade_delete_user |
int $user_id |
src/Services/DataCascadeHandler.php:257 |
// Clean up custom data when a service is deletedadd_action( 'wpss_before_cascade_delete_service', function( $service_id ) { global $wpdb; $wpdb->delete( $wpdb->prefix . 'my_custom_table', [ 'service_id' => $service_id ] );} );Vendor Actions
Section titled “Vendor Actions”Vendor Filters
Section titled “Vendor Filters”| Filter | Parameters | File |
|---|---|---|
wpss_pre_vendor_register |
array $profile_data, int $user_id |
VendorService.php |
wpss_vendor_profile_allowed_fields |
array $allowed_fields |
VendorService.php |
wpss_vendor_pitch_stats |
array $stats |
src/functions/vendors.php:838 |
wpss_vendor_pitch_steps |
array $steps |
src/Frontend/Shortcodes.php:1349 |
| Hook | Parameters | File |
|---|---|---|
wpss_vendor_registered |
int $user_id, array $profile_data |
src/Services/VendorService.php:182 |
wpss_vendor_profile_updated |
int $user_id, array $filtered_data |
src/Services/VendorService.php:561 |
wpss_vendor_vacation_mode_changed |
int $user_id, bool $enabled, string $message |
src/Services/VendorService.php:612 |
wpss_vendor_tier_changed |
int $user_id, string $tier |
src/Services/VendorService.php:653 |
wpss_vendor_level_promoted |
int $user_id, string $new_level, string $current_level |
src/Services/SellerLevelService.php:377 |
wpss_vendor_level_updated |
int $user_id, string $level |
src/Services/SellerLevelService.php:274 |
wpss_vendor_status_updated |
int $vendor_id, string $status |
src/Services/VendorService.php:377 |
wpss_vendor_commission_updated |
int $vendor_id, float $rate |
src/Admin/Pages/VendorsPage.php:1405 |
wpss_vendor_contacted |
int $vendor_id, int $user_id, int $service_id, string $message, array $attachments |
src/Frontend/AjaxHandlers.php:2095 |
wpss_vendor_access_granted |
int $user_id |
src/Services/VendorService.php:442 |
wpss_vendor_access_revoked |
int $user_id |
src/Services/VendorService.php:494 |
Financial Actions
Section titled “Financial Actions”Financial Filters
Section titled “Financial Filters”| Filter | Parameters | File |
|---|---|---|
wpss_commission_base_amount |
float $base_amount, int $order_id, int $vendor_id |
CommissionService.php |
| Hook | Parameters | File |
|---|---|---|
wpss_commission_recorded |
int $order_id, array $commission, int $vendor_id |
src/Services/CommissionService.php:297 |
wpss_withdrawal_requested |
int $withdrawal_id, int $vendor_id, float $amount |
src/Services/EarningsService.php:788 |
wpss_withdrawal_processed |
int $withdrawal_id, string $status, object $withdrawal |
src/Services/EarningsService.php:524 |
wpss_auto_withdrawal_created |
int $withdrawal_id, int $vendor_id, float $amount |
src/Services/EarningsService.php:1634 |
wpss_tip_order_created |
int $tip_order_id, int $parent_order_id, int $customer_id, float $amount |
TippingService.php:290 |
wpss_tip_sent |
int $tip_txn_id, int $parent_order_id, int $vendor_id, int $customer_id, float $vendor_earnings, string $vendor_notes |
src/Services/TippingService.php:490 |
wpss_tip_order_created fires when the tip checkout is started; wpss_tip_sent
fires only once the tip is actually paid and credited. The first argument of
wpss_tip_sent is the wallet transaction id, not the tip order id, and the
amount passed is the vendor’s net earnings after commission – not the gross
tip. Tips are excluded from commission by default, so the two usually match.
Dispute Actions
Section titled “Dispute Actions”Dispute Filters
Section titled “Dispute Filters”| Filter | Parameters | File |
|---|---|---|
wpss_pre_open_dispute |
array $dispute_data, int $order_id |
DisputeService.php |
| Hook | Parameters | File |
|---|---|---|
wpss_dispute_opened |
int $dispute_id, int $order_id, int $opened_by, array $data |
src/Services/DisputeService.php:407 |
wpss_dispute_evidence_added |
int $dispute_id, int $user_id |
src/Services/DisputeService.php:546 |
wpss_dispute_status_changed |
int $dispute_id, string $status, string $old_status |
src/Services/DisputeService.php:842 |
wpss_dispute_resolved |
int $dispute_id, string $resolution, object $dispute, float $refund_amount |
src/Services/DisputeService.php:1043 |
wpss_dispute_response_submitted |
int $message_id, int $dispute_id, int $user_id |
src/Services/DisputeWorkflowManager.php:208 |
wpss_dispute_escalated |
int $dispute_id, string $reason, int $escalated_by |
src/Services/DisputeWorkflowManager.php:358 |
wpss_dispute_cancelled |
int $dispute_id, int $user_id, string $reason |
src/Services/DisputeWorkflowManager.php:524 |
Review, Request, and Proposal Actions
Section titled “Review, Request, and Proposal Actions”Review Filters
Section titled “Review Filters”| Filter | Parameters | File |
|---|---|---|
wpss_pre_create_review |
array $review_data, int $order_id |
ReviewService.php |
| Hook | Parameters | File |
|---|---|---|
wpss_review_created |
int $review_id, int $order_id |
src/API/ReviewsController.php:462 |
wpss_review_reply_created |
int $review_id |
src/API/ReviewsController.php:621 |
wpss_buyer_request_created |
int $post_id, array $data |
src/Services/BuyerRequestService.php:121 |
wpss_buyer_request_updated |
int $request_id, array $data |
src/Services/BuyerRequestService.php:174 |
wpss_buyer_request_status_changed |
int $request_id, string $status, string $old_status |
src/Services/BuyerRequestService.php:645 |
wpss_request_converted_to_order |
int $order_id, int $request_id, int $proposal_id, object $request, object $proposal |
src/Services/BuyerRequestService.php:1182 |
wpss_proposal_submitted |
int $proposal_id, int $request_id, int $vendor_id, array $proposal_data |
src/Services/ProposalService.php:187 |
wpss_proposal_updated |
int $proposal_id, array $update_data |
src/Services/ProposalService.php:365 |
wpss_proposal_accepted |
int $proposal_id, object $proposal, object $request |
src/Services/BuyerRequestService.php:1124 |
wpss_proposal_rejected |
int $proposal_id, object $proposal, string $reason |
src/Services/ProposalService.php:458 |
wpss_proposal_withdrawn |
int $proposal_id, object $proposal |
src/Services/ProposalService.php:503 |
wpss_proposal_deleted |
int $proposal_id, object $proposal |
src/Services/ProposalService.php:895 |
wpss_proposal_status_updated |
int $proposal_id, string $status |
src/Services/ProposalService.php:593 |
wpss_buyer_request_deleted |
int $request_id |
src/Services/BuyerRequestService.php:1351 |
wpss_buyer_request_meta_saved |
int $post_id, WP_Post $post |
src/Admin/Metaboxes/BuyerRequestMetabox.php:349 |
Milestone and Extension Actions
Section titled “Milestone and Extension Actions”A milestone is a sub-order of the parent order, so every hook passes both
ids: $milestone_id is the sub-order, $order_id is the parent. See
Sub-Order Pattern.
| Hook | Parameters | File |
|---|---|---|
wpss_milestone_proposed |
int $milestone_id, int $order_id, int $vendor_id |
src/Services/MilestoneService.php:262 |
wpss_milestone_paid |
int $milestone_id, int $order_id, int $vendor_id, int $customer_id, float $vendor_earnings |
src/Services/MilestoneService.php:337 |
wpss_milestone_submitted |
int $milestone_id, int $order_id, int $vendor_id, int $customer_id |
src/Services/MilestoneService.php:511 |
wpss_milestone_approved |
int $milestone_id, int $order_id, int $vendor_id, int $customer_id |
src/Services/MilestoneService.php:567 |
wpss_milestone_declined |
int $milestone_id, int $order_id, int $customer_id |
src/Services/MilestoneService.php:741 |
wpss_milestone_revision_requested |
int $milestone_id, int $parent_id, int $vendor_id, int $customer_id, string $reason |
src/Services/MilestoneService.php:686 |
wpss_extension_request_created |
int $request_id, int $order_id, array $data (requested_by, extra_days, reason) |
ExtensionRequestService.php:249 |
wpss_extension_request_approved |
int $request_id, object $request |
ExtensionRequestService.php:371 |
wpss_extension_request_rejected |
int $request_id, object $request |
ExtensionRequestService.php:455 |
wpss_extension_approved |
int $pay_order_id, int $parent_order_id, int $vendor_id, int $customer_id, float $vendor_earnings, int $extra_days, int $request_id |
Paid extension approved and the vendor credited. NET earnings, not gross. ExtensionOrderService.php |
Renamed in 1.3.0. The milestone lifecycle uses proposed and declined, not created and rejected – see Milestone terminology.
wpss_milestone_created,wpss_milestone_rejectedandwpss_extension_requestedwere listed in earlier docs but are not fired by the plugin. Callbacks bound to those names never run.
wpss_extension_approvedis a different case: it does fire, with seven arguments, when a paid extension is approved and the vendor is credited. It was wrongly grouped with the three above. See the extensions table. Note also thatwpss_milestone_approvedpasses$vendor_idas its third argument, not an amount – if you need the money, read it from the sub-order or hookwpss_milestone_paid.
Admin and Settings Actions
Section titled “Admin and Settings Actions”These hooks fire in the WordPress admin area for order management, service meta, and settings pages.
| Hook | Parameters | File |
|---|---|---|
wpss_admin_order_actions |
object $order, string $status |
src/Admin/Admin.php:2805 |
wpss_gateway_cards |
Settings $settings |
src/Admin/Settings.php:2052 |
Admin Filters
Section titled “Admin Filters”| Filter | Parameters | File |
|---|---|---|
wpss_service_meta_fields |
array $fields, int $post_id |
src/Admin/Metaboxes/ServiceMetabox.php:189 |
wpss_pro_upgrade_url |
string $url (default https://wpsellservices.com/) |
UpgradePage.php, ServiceWizard.php:961 |
wpss_docs_url |
string $url (default https://wpsellservices.com/docs/) |
src/Admin/Pages/UpgradePage.php:347 |
wpss_pro_upgrade_url (added 1.3.0) controls where every “Upgrade to Pro” call-to-action points — the admin upgrade screen and the in-wizard prompts. Point it at your own landing page or an in-site URL:
add_filter( 'wpss_pro_upgrade_url', function( $url ) { return home_url( '/go-pro/' );} );Dashboard Menu Visibility Filters
Section titled “Dashboard Menu Visibility Filters”Added in 1.3.0. Role-based menu visibility lets you show or hide dashboard sections per user role. Gate a section programmatically with:
| Filter | Parameters | File |
|---|---|---|
wpss_can_access_dashboard_section |
bool $can_access, string $section, int $user_id |
MenuVisibility.php |
// Hide the "Earnings" section from a custom roleadd_filter( 'wpss_can_access_dashboard_section', function( $can, $section, $user_id ) { if ( 'earnings' === $section && user_can( $user_id, 'my_limited_role' ) ) { return false; } return $can;}, 10, 3 );Currency Display Filters
Section titled “Currency Display Filters”| Filter | Parameters | File |
|---|---|---|
wpss_catalog_price_html |
string $html, float $amount, string $context |
src/functions/money.php:165 |
wpss_catalog_price_html (added 1.3.0) is the single seam for catalog price display. Base currency is authoritative for all stored amounts; this filter is where an add-on (such as the Pro display-currency hint) injects a converted, visitor-facing price without changing the stored value.
// Add custom fields to the service meta box in wp-adminadd_filter( 'wpss_service_meta_fields', function( $fields, $post_id ) { $fields['custom_field'] = [ 'label' => 'Custom Field', 'type' => 'text', 'value' => get_post_meta( $post_id, '_wpss_custom_field', true ), ]; return $fields;}, 10, 2 );Service Wizard Actions
Section titled “Service Wizard Actions”| Hook | Parameters | File |
|---|---|---|
wpss_service_wizard_saved |
int $service_id, array $sanitized_data |
src/Frontend/ServiceWizard.php:1943 |
wpss_wizard_pricing_after |
WP_Post|null $service |
ServiceWizard.php |
wpss_wizard_save_service_meta |
int $service_id, array $data |
ServiceWizard.php |
Service Wizard Filters
Section titled “Service Wizard Filters”| Filter | Parameters | File |
|---|---|---|
wpss_vendor_can_create_service |
bool $can_create, int $user_id |
src/API/ServicesController.php:1383 |
wpss_services_per_page |
int $per_page (default 12) |
src/Frontend/ServiceArchiveView.php:640 |
wpss_wizard_service_data |
array $data, int $service_id |
ServiceWizard.php |
wpss_wizard_sanitize_service_data |
array $sanitized, array $raw |
ServiceWizard.php |
// Prevent unverified vendors from creating servicesadd_filter( 'wpss_vendor_can_create_service', function( $allowed, $user_id ) { if ( ! get_user_meta( $user_id, '_wpss_identity_verified', true ) ) { return false; } return $allowed;}, 10, 2 );Wizard Extension Surface (Add-on Integration — 1.2.0)
Section titled “Wizard Extension Surface (Add-on Integration — 1.2.0)”These four hooks form the contract that lets add-ons (such as Pro’s recurring billing) inject fields into the frontend Create/Edit Service wizard without patching core files.
wpss_wizard_service_data — seed extra keys into the wizard’s edit-form data model so they pre-fill when a vendor edits an existing service:
add_filter( 'wpss_wizard_service_data', function( $data, $service_id ) { $data['my_billing_cycle'] = get_post_meta( $service_id, '_wpss_billing_cycle', true ) ?: 'one_time'; return $data;}, 10, 2 );wpss_wizard_pricing_after — render extra fields after the Pricing step. Markup runs inside the wizard’s Alpine.js scope; bind inputs with x-model="data.your_key":
add_action( 'wpss_wizard_pricing_after', function( $service ) { ?> <div class="wpss-field-group"> <label><?php esc_html_e( 'Billing cycle', 'my-addon' ); ?></label> <select x-model="data.my_billing_cycle"> <option value="one_time"><?php esc_html_e( 'One-time', 'my-addon' ); ?></option> <option value="monthly"><?php esc_html_e( 'Monthly', 'my-addon' ); ?></option> </select> </div> <?php} );wpss_wizard_sanitize_service_data — sanitize your injected keys from the untrusted client JSON payload before save:
add_filter( 'wpss_wizard_sanitize_service_data', function( $sanitized, $raw ) { $allowed = [ 'one_time', 'monthly', 'yearly' ]; $sanitized['my_billing_cycle'] = in_array( $raw['my_billing_cycle'] ?? '', $allowed, true ) ? $raw['my_billing_cycle'] : 'one_time'; return $sanitized;}, 10, 2 );wpss_wizard_save_service_meta — persist your custom meta. Fires on both save-draft and publish (unlike wpss_service_wizard_saved, which fires on publish only):
add_action( 'wpss_wizard_save_service_meta', function( $service_id, $data ) { if ( isset( $data['my_billing_cycle'] ) ) { update_post_meta( $service_id, '_wpss_billing_cycle', $data['my_billing_cycle'] ); }}, 10, 2 );Dashboard Actions
Section titled “Dashboard Actions”| Hook | Parameters | File |
|---|---|---|
wpss_dashboard_section_before_content |
string $section, int $user_id |
src/Frontend/UnifiedDashboard.php:957 |
Dashboard Filters
Section titled “Dashboard Filters”| Filter | Parameters | File |
|---|---|---|
wpss_dashboard_default_section |
string $section, int $user_id |
UnifiedDashboard.php |
// Add a notice at the top of the earnings dashboard sectionadd_action( 'wpss_dashboard_section_before_content', function( $section, $user_id ) { if ( 'earnings' === $section ) { echo '<div class="wpss-notice">Minimum withdrawal is $50.</div>'; }}, 10, 2 );wpss_dashboard_default_section — the landing section when no section is in the URL. Defaults to sales for active vendors and orders for buyers:
// Always land on the messages section regardless of roleadd_filter( 'wpss_dashboard_default_section', function( $section, $user_id ) { return 'messages';}, 10, 2 );Cart and Checkout Filters
Section titled “Cart and Checkout Filters”| Filter | Parameters | File |
|---|---|---|
wpss_add_service_to_cart |
bool $added, array $cart_item, object $adapter |
src/Frontend/AjaxHandlers.php:2331 |
wpss_pay_order_url_lookup |
string $url, int $order_id, ?object $order |
src/functions/urls.php |
wpss_ensure_pay_order |
string $url, int $order_id |
src/functions/urls.php |
The pay-order seam: wpss_pay_order_url_lookup + wpss_ensure_pay_order
Section titled “The pay-order seam: wpss_pay_order_url_lookup + wpss_ensure_pay_order”This is the one seam for “send the buyer somewhere they can pay THIS
order.” Every tip, milestone phase, paid extension and accepted proposal
resolves its pay link through it – on the order page, in the dashboard, in the
REST checkout_url field, and in the emails the plugin sends.
Never rebuild that URL inline. Code that does is correct on the standalone rail and broken on every other one.
// Read: where the buyer will pay. Safe once per row on any list or page.$url = wpss_get_pay_order_url( $order_id ); // src/functions/urls.php// Write: the buyer is paying now. The rail creates its store order here.$url = wpss_ensure_pay_order( $order_id ); // src/functions/urls.phpDefault (unfiltered): /<checkout page>/pay/{id}/ on the standalone rail
(?pay_order={id} without pretty permalinks). On a store rail the read helper
returns <WPSS checkout page>?pay_order={id}, a dormant page that resolves
through wpss_ensure_pay_order() on template_redirect - so the store order
is created when the buyer clicks, not when a list is rendered.
Why it needs a filter at all: a cart-based rail has no concept of “pay this
existing order.” Appending ?pay_order=N to a WooCommerce checkout URL lands
the buyer on an empty cart with no way to pay and no error message – which
is exactly what every tip, milestone and extension link did in Woo mode before
1.4.0.
Pay-order support by rail
Section titled “Pay-order support by rail”This table is the single source of truth for which rails can pay one existing order. Every other page links here rather than restating it – the copies that used to live in the REST, WooCommerce and coverage pages each drifted separately, and three of them were still saying FluentCart had no pay-order rail after it gained one.
| Rail | Can pay one existing order? | Resolver | Caveats |
|---|---|---|---|
| Standalone | Yes, natively | none needed – /pay/N/ on the plugin’s own checkout |
Lock-step milestone order is enforced here |
| WooCommerce | Yes | WCPayOrderResolver [PRO] |
Creates or reuses a wc-pending WC order; lock-step is not enforced on the WC order-pay URL |
| FluentCart | Yes | FluentCartPayOrderResolver [PRO] |
Beta rail, off until wpss_pro_beta_rails returns true; mints a FluentCart order carrying one fee line; lock-step is not enforced on its checkout URL |
| EDD | No | none | No Pay button at all – wpss_can_pay_single_order() is false, so tips, phases and extensions cannot be charged on this rail |
Both Pro resolvers hook the read and the write half: wpss_pay_order_url_lookup
rebuilds a URL from what the WPSS row already knows, wpss_ensure_pay_order
creates the store order when the buyer clicks.
Two things to know before you hook it:
- The read filter must not write.
wpss_get_pay_order_url()runs once per unpaid row on every order list, order page, REST shape and email.wpss_pay_order_url_lookupmay only return a URL the rail already knows (Pro stores the WC order id and key on the WPSS row when it creates the order). Creating the store order belongs inwpss_ensure_pay_order, which runs on click and on the writes that create a tip, phase or extension. Make it idempotent, asWCPayOrderResolveris. - It is not a security gate. The milestone lock-step guard lives on the
checkout side, not here.
wpss_can_pay_single_order()is true when a rail hooks either filter.
// Point a custom rail at its own pay page.add_filter( 'wpss_pay_order_url_lookup', function ( string $url, int $order_id, ?object $order ): string { $known = $order->meta['my_gateway_invoice'] ?? ''; return $known ? my_gateway_pay_page_url( $known ) : $url; // read only}, 10, 3 );
add_filter( 'wpss_ensure_pay_order', function ( string $url, int $order_id ): string { $order = wpss_get_order( $order_id ); if ( ! $order || 'paid' === ( $order->payment_status ?? '' ) ) { return $url; } return my_gateway_create_or_reuse_invoice( $order ); // the write}, 10, 2 );See WooCommerce Checkout for the full WC implementation.
Email Filters
Section titled “Email Filters”These filters let you customize outgoing email content without modifying templates.
| Filter | Parameters | File |
|---|---|---|
wpss_email_from_name |
string $from_name |
src/Services/EmailService.php:2106 |
wpss_email_header_vars |
array $template_vars, string $type |
src/Services/EmailService.php:2082 |
wpss_vendor_pending_email_content |
string $content, object $user, string $platform_name |
src/Services/NotificationService.php:1570 |
// Change the "From" name on all marketplace emailsadd_filter( 'wpss_email_from_name', function( $name ) { return 'DesignHub Marketplace';} );
// Customize the "vendor application received" email contentadd_filter( 'wpss_vendor_pending_email_content', function( $content, $user, $platform ) { $content .= '<p>We review applications within two business days.</p>'; return $content;}, 10, 3 );Other Actions
Section titled “Other Actions”Other Filters
Section titled “Other Filters”| Filter | Parameters | File |
|---|---|---|
wpss_pre_send_message |
array $message_data, int $conversation_id |
ConversationService.php |
wpss_validate_add_to_cart |
bool $valid, int $service_id, int $package_id, int $user_id |
CartController.php |
wpss_cart_item_data |
array $cart_item, int $service_id, int $package_id |
CartController.php |
wpss_email_subject |
string $subject, string $type, string $to |
EmailService.php |
wpss_search_query_args |
array $query_args, string $query |
SearchService.php |
wpss_template_args |
array $args, string $template_name |
functions.php |
| Hook | Parameters | File |
|---|---|---|
wpss_message_sent |
object $message, object $conversation |
src/Services/ConversationService.php:342 |
wpss_notification_created |
int $notification_id, int $user_id, string $type, array $data |
src/Services/NotificationService.php:112 |
wpss_portfolio_item_created |
int $item_id, int $vendor_id, array $data |
src/Services/PortfolioService.php:199 |
wpss_portfolio_item_updated |
int $item_id, array $data |
src/Services/PortfolioService.php:294 |
wpss_portfolio_item_deleted |
int $item_id, object $item |
src/Services/PortfolioService.php:344 |
wpss_settings_tab_{tab} |
(none) | Settings.php:985 |
wpss_advanced_settings_sections |
(none) | src/Admin/Settings.php:2173 |
Filters
Section titled “Filters”Provider Registration
Section titled “Provider Registration”| Filter | File | Default |
|---|---|---|
wpss_ecommerce_adapters |
IntegrationManager.php:67 |
Standalone only (Pro adds WooCommerce, EDD, FluentCart) |
wpss_payment_gateways |
Plugin.php:813 |
Test gateway (debug) |
wpss_wallet_providers [PRO] |
Plugin.php:825 |
Empty |
wpss_storage_providers [PRO] |
Plugin.php:837 |
Empty |
wpss_email_providers [PRO] |
Plugin.php:849 |
Empty |
wpss_analytics_widgets [PRO] |
Plugin.php:861 |
Empty |
Service Wizard Limits
Section titled “Service Wizard Limits”| Filter | File | Free Default |
|---|---|---|
wpss_service_max_packages |
ServiceWizard.php:116 |
3 |
wpss_service_max_gallery |
ServiceWizard.php:126 |
4 |
wpss_service_max_videos |
ServiceWizard.php:136 |
1 |
wpss_service_max_extras |
ServiceWizard.php:146 |
3 |
wpss_service_max_faq |
ServiceWizard.php:156 |
5 |
wpss_service_max_requirements |
ServiceWizard.php:166 |
5 |
Data Filters
Section titled “Data Filters”| Filter | Parameters | File |
|---|---|---|
wpss_format_price |
$formatted, $price, $currency |
src/functions/money.php:57 |
wpss_currency |
$currency |
src/functions/money.php:1089 |
wpss_platform_name |
$platform_name |
src/functions/misc.php:36 |
wpss_is_vendor |
$is_vendor, $user_id |
src/functions/vendors.php:147 |
wpss_order_number_prefix |
$prefix (default 'WPSS-') |
src/Database/Repositories/OrderRepository.php:92 |
wpss_currency_symbols |
$symbols |
src/functions/money.php:1124 |
wpss_currency_format |
$format, $symbol, $currency |
src/functions/money.php:1151 |
wpss_currencies |
$currencies |
src/functions/money.php:1946 |
wpss_order_statuses |
$statuses |
src/functions/orders.php:153 |
wpss_max_upload_size |
$upload_max |
src/functions/misc.php:126 |
wpss_allow_late_requirements_submission |
$allow_late |
src/functions/orders.php:820 |
wpss_wallet_manager |
null |
src/functions/money.php:1968 |
Currency System Filters (1.2.1)
Section titled “Currency System Filters (1.2.1)”As of 1.2.1, currencies are driven by a single canonical registry (code → name, symbol, decimals). Every currency surface — price formatting, the settings dropdown, manual orders, decimal handling — reads from it, so overriding one filter updates all of them consistently.
| Filter | Parameters | File |
|---|---|---|
wpss_currency_registry |
array<string, array{name:string, symbol:string, decimals:int}> $registry |
src/functions/money.php:1925 |
wpss_currency_decimals |
int $decimals, string $currency |
src/functions/money.php:205 |
wpss_zero_decimal_currencies |
string[] $codes |
src/functions/money.php:1051 |
wpss_settings_currencies |
array $currencies |
src/Admin/Settings.php:3998 |
wpss_manual_order_currencies |
array $currencies |
src/Admin/Pages/ManualOrderPage.php:948 |
wpss_currency_registry is the preferred, single-place override — add, remove, or adjust a currency (name / symbol / decimals) and every currency surface updates. Prefer it over the older per-surface currency filters (wpss_currency_symbols, wpss_currency_format, wpss_currencies):
// Register a custom currency and change USD's symboladd_filter( 'wpss_currency_registry', function( $registry ) { $registry['XCD'] = [ 'name' => 'East Caribbean Dollar', 'symbol' => 'EC$', 'decimals' => 2, ]; $registry['USD']['symbol'] = 'US$'; return $registry;} );wpss_currency_decimals overrides the decimal places for a specific currency at format time (for example, to render USD without minor units):
add_filter( 'wpss_currency_decimals', function( $decimals, $currency ) { return 'USD' === $currency ? 0 : $decimals;}, 10, 2 );wpss_zero_decimal_currencies returns the codes rendered without minor units. It is derived from the registry (decimals === 0); filter it only when you need to force a currency into or out of zero-decimal formatting independently of its registry entry.
wpss_settings_currencies and wpss_manual_order_currencies narrow (or extend) the currency choices offered in the admin settings dropdown and the manual-order screen respectively — useful for restricting a store to a subset of the registry:
// Only allow USD and EUR to be selected in settingsadd_filter( 'wpss_settings_currencies', function( $currencies ) { return array_intersect_key( $currencies, array_flip( [ 'USD', 'EUR' ] ) );} );Template Filters
Section titled “Template Filters”| Filter | Parameters | File |
|---|---|---|
wpss_get_template_part |
$template, $slug, $name |
src/functions/templates.php:125 |
wpss_get_template |
$template, $template_name, $args |
src/functions/templates.php:176 |
wpss_locate_template |
$template, $template_name, $template_path |
src/Frontend/TemplateLoader.php:568 |
wpss_dashboard_section_template |
$template_path, $section |
src/functions/urls.php:415 |
URL and Taxonomy Filters
Section titled “URL and Taxonomy Filters”| Filter | Parameters | File |
|---|---|---|
wpss_vendor_slug |
$slug (default 'provider') |
Plugin.php, functions.php |
wpss_service_order_slug |
$slug (default 'service-order') |
Plugin.php, functions.php |
wpss_checkout_slug |
$slug (default 'service-checkout') |
StandaloneAdapter.php |
wpss_service_slug |
$slug (default 'service') |
src/PostTypes/ServicePostType.php:424 |
wpss_buyer_request_slug |
$slug (default 'buyer-request') |
src/PostTypes/BuyerRequestPostType.php:114 |
wpss_service_post_type_args |
$args |
src/PostTypes/ServicePostType.php:346 |
wpss_service_tag_args |
$args |
src/PostTypes/ServicePostType.php:408 |
wpss_service_category_taxonomy_args |
$args |
ServiceCategoryTaxonomy.php:118 |
wpss_service_tag_taxonomy_args |
$args |
ServiceTagTaxonomy.php:103 |
wpss_buyer_request_post_type_args |
$args |
src/PostTypes/BuyerRequestPostType.php:98 |
Order, Commission, and API Filters
Section titled “Order, Commission, and API Filters”| Filter | Parameters | File |
|---|---|---|
wpss_order_status_transitions |
$transitions, $from, $to |
src/Services/OrderService.php:1025 |
wpss_commission_rate |
$rate, $order, $vendor_id, $service_id |
src/Services/CommissionService.php:362 |
wpss_proposal_order_revisions |
$revisions, $proposal, $request |
src/Services/BuyerRequestService.php:1006 |
wpss_max_order_quantity |
$max |
src/Frontend/SingleServiceView.php:960 |
wpss_api_controllers |
$controllers |
src/API/API.php:183 |
wpss_api_public_settings |
$settings |
src/API/API.php:659 |
wpss_batch_max_requests |
$max (default 25) |
src/API/API.php:1363 |
wpss_api_cors_origins |
$origins |
src/API/API.php:1436 |
wpss_settings_tabs |
$tabs |
src/Admin/Settings.php:239 |
wpss_blocks |
$blocks |
src/Blocks/BlocksManager.php:95 |
wpss_rate_limits |
$limits, $action |
src/Core/RateLimiter.php:289 |
Miscellaneous Filters
Section titled “Miscellaneous Filters”| Filter | Parameters | File |
|---|---|---|
wpss_realtime_settings |
array $settings |
RealtimeService.php |
wpss_review_window_days |
$days |
src/Services/ReviewService.php:519 |
wpss_auto_approve_reviews |
$auto_approve (default true) |
src/API/ReviewsController.php:426 |
wpss_vendor_registration_open |
$open (default true) |
src/API/VendorsController.php:599 |
wpss_auto_approve_vendors |
$auto_approve (default true) |
src/Services/VendorService.php:112 |
wpss_delivery_allowed_file_types |
$allowed |
src/functions/files.php:373 |
wpss_requirements_allowed_file_types |
$allowed |
src/functions/files.php:383 |
wpss_withdrawal_methods |
$methods |
src/API/EarningsController.php:705 |
wpss_search_results |
$results, $query, $args |
SearchService.php:121 |
wpss_search_suggestions |
$suggestions, $query |
src/Services/SearchService.php:482 |
wpss_related_services_args |
$args, $service |
src/Frontend/SingleServiceView.php:794 |
wpss_cart_checkout |
$result, $cart, $user_id, $payment_method |
src/API/CartController.php:435 |
wpss_seller_levels |
$levels |
src/API/SellerLevelsController.php:263 |
wpss_rest_service_data |
$data, $service, $request |
src/API/ServicesController.php:1499 |
wpss_rest_order_data |
$data, $order, $request |
OrdersController.php |
wpss_rest_review_data |
$data, $review, $request |
ReviewsController.php |
wpss_rest_vendor_data |
$data, $vendor, $request |
VendorsController.php |
wpss_can_access_dashboard_section |
$allowed, $section, $user_id |
src/Frontend/UnifiedDashboard.php:478 |
wpss_dashboard_sections |
$sections, $user_id, $is_vendor |
src/Frontend/UnifiedDashboard.php:577 |
wpss_dashboard_section_titles |
$titles |
src/Frontend/UnifiedDashboard.php:896 |
wpss_realtime_settings — filter the resolved real-time/WebSocket connection settings before they are used. The $settings array includes: enabled, app_id, key, secret, host, cluster, port, use_tls. The secret field is server-only; it is never sent to the browser:
add_filter( 'wpss_realtime_settings', function( $settings ) { // Force a specific cluster at runtime $settings['cluster'] = 'eu'; return $settings;} );Full-width Plugin Pages Filters
Section titled “Full-width Plugin Pages Filters”| Filter | Parameters | File |
|---|---|---|
wpss_use_fullwidth_template |
bool $use |
src/Frontend/TemplateLoader.php:292 (and :332) |
wpss_fullwidth_page_keys |
string[] $page_keys |
src/Frontend/TemplateLoader.php:338 |
wpss_use_fullwidth_template — return false to keep the active theme’s normal page template (with sidebar) on the plugin’s pages instead of the sidebar-free full-width layout:
add_filter( 'wpss_use_fullwidth_template', '__return_false' );wpss_fullwidth_page_keys — control which mapped plugin pages render full-width. Default: ['dashboard', 'cart', 'checkout', 'become_vendor', 'registration']:
// Remove the cart page from full-width treatmentadd_filter( 'wpss_fullwidth_page_keys', function( $keys ) { return array_diff( $keys, [ 'cart' ] );} );SEO and Email Filters
Section titled “SEO and Email Filters”| Filter | Parameters | File |
|---|---|---|
wpss_service_schema |
$schema, $service_id |
src/SEO/SchemaMarkup.php:202 |
wpss_service_list_schema |
$schema |
src/SEO/SchemaMarkup.php:242 |
wpss_category_schema |
$schema, $term |
src/SEO/SchemaMarkup.php:301 |
wpss_person_schema |
$schema, $user_id |
src/SEO/SchemaMarkup.php:354 |
wpss_vendor_page_schema |
$schema, $user_id |
src/SEO/SchemaMarkup.php:401 |
wpss_organization_schema |
$schema |
src/SEO/SchemaMarkup.php:432 |
wpss_open_graph_data |
$data, $service_id |
src/SEO/SEO.php:270 |
wpss_sitemap_post_types |
$post_types |
src/SEO/SEO.php:334 |
wpss_breadcrumbs |
$breadcrumbs, $service_id |
src/SEO/SEO.php:402 |
wpss_notification_email_content |
$content, $subject, $user_id, $data |
src/Services/NotificationService.php:2013 |
wpss_vendor_welcome_email_content |
$content, $user, $platform_name |
src/Services/NotificationService.php:1521 |
wpss_admin_vendor_notification_content |
$content, $user |
src/Services/NotificationService.php:1623 |
Pro Plugin Actions [PRO]
Section titled “Pro Plugin Actions [PRO]”These hooks are fired exclusively by the Pro plugin and require an active Pro license.
WooCommerce Integration Actions
Section titled “WooCommerce Integration Actions”Unlike the EDD and FluentCart adapters, the WooCommerce adapter does
not fire its own namespaced lifecycle hooks. It reuses the core order hooks
instead, so code written against wpss_order_created /
wpss_order_status_changed works identically whether the sale came through
WooCommerce or standalone checkout.
| Hook | Parameters | File |
|---|---|---|
wpss_order_created |
int $order_id, string $status |
WCOrderProvider.php |
wpss_order_status_changed |
int $order_id, string $new_status, string $old_status |
WCOrderProvider.php |
wpss_max_order_quantity |
int $max, int $service_id |
WCCheckoutProvider.php |
Earlier documentation listed
wpss_woocommerce_adapter_init,wpss_service_synced_to_wc_product,wpss_after_checkout_processandwpss_service_to_wc_status_map. None of these are fired – use the core order hooks above. To react to product sync, hookwpss_service_updated.
EDD Integration Actions
Section titled “EDD Integration Actions”| Hook | Parameters | File |
|---|---|---|
wpss_edd_adapter_init |
EDDAdapter $adapter |
src/Integrations/EDD/EDDAdapter.php:166 |
wpss_edd_service_purchased |
ServiceItem $item, int $order_id |
src/Integrations/EDD/EDDOrderProvider.php:119 |
wpss_edd_services_processed |
int $order_id, ServiceItem[] $items |
src/Integrations/EDD/EDDOrderProvider.php:149 |
wpss_edd_order_record_created |
int $record_id, ServiceItem $item, int $order_id |
src/Integrations/EDD/EDDOrderProvider.php:410 |
wpss_edd_service_meta_saved |
int $product_id |
src/Integrations/EDD/EDDProductProvider.php:237 |
wpss_edd_service_checkout_processed |
int $order_id, int $download_id, array $service_data, int $index |
EDDCheckoutProvider.php:222 |
FluentCart Integration Actions
Section titled “FluentCart Integration Actions”| Hook | Parameters | File |
|---|---|---|
wpss_fluentcart_adapter_init |
FluentCartAdapter $adapter |
src/Integrations/FluentCart/FluentCartAdapter.php:213 |
wpss_fluentcart_order_created |
int $order_id, int $external_order_id, array $order_data |
src/Integrations/FluentCart/FluentCartOrderProvider.php:110 |
wpss_fluentcart_order_detail |
object $order |
src/Integrations/FluentCart/FluentCartAccountProvider.php:366 |
wpss_fluentcart_product_createdwas removed in 1.6.1. FluentCart is a payment rail, not a catalogue: the plugin no longer creates FluentCart products, so there is no creation event to fire. A service is linked to an existing FluentCart product instead.
SureCart integration removed in 1.6.0. Its four namespaced hooks (
wpss_surecart_adapter_init,wpss_surecart_order_created,wpss_surecart_product_created,wpss_surecart_order_detail) no longer exist. SureCart keeps products and prices as objects in its own cloud and settles through webhooks, so it cannot act as a payment rail the way WooCommerce, EDD and FluentCart do – charging an arbitrary amount would mean creating a remote price object per order.
Wallet Actions
Section titled “Wallet Actions”| Hook | Parameters | File |
|---|---|---|
wpss_terawallet_recharged |
int $transaction_id, float $amount |
TeraWalletProvider.php:203 |
wpss_mycred_balance_changed |
int $user_id, float $amount, string $reference |
MyCredProvider.php:253 |
Razorpay Actions
Section titled “Razorpay Actions”| Hook | Parameters | File |
|---|---|---|
wpss_razorpay_refund_processed |
string $payment_id, array $refund |
src/Integrations/Razorpay/RazorpayGateway.php:885 |
Stripe Connect Actions
Section titled “Stripe Connect Actions”| Hook | Parameters | File |
|---|---|---|
wpss_pro_connect_payout_paid |
string $payout_id, string $account_id, float $amount, string $currency |
src/StripeConnect/ConnectWebhookHandler.php:253 |
wpss_pro_connect_payout_failed |
string $payout_id, string $account_id, string $failure_code, string $failure_message |
src/StripeConnect/ConnectWebhookHandler.php:294 |
wpss_pro_connect_transfer_created |
string $transfer_id, string $account_id, float $amount, string $currency |
src/StripeConnect/ConnectWebhookHandler.php:335 |
Recurring Services Actions
Section titled “Recurring Services Actions”| Hook | Parameters | File |
|---|---|---|
wpss_recurring_renewal_order_created |
int $new_order_id, int $subscription_id, object $subscription |
src/RecurringServices/RecurringOrderFactory.php:139 |
wpss_recurring_payment_failed |
int $subscription_id, object $subscription |
RecurringWebhookHandler.php:191 |
wpss_recurring_subscription_cancelled |
int $subscription_id, object $subscription |
RecurringWebhookHandler.php:229 |
Analytics Actions
Section titled “Analytics Actions”| Hook | Parameters | File |
|---|---|---|
wpss_analytics_init |
AnalyticsManager $manager |
src/Analytics/AnalyticsManager.php:103 |
Gateway Settings Actions
Section titled “Gateway Settings Actions”| Hook | Parameters | File |
|---|---|---|
wpss_gateway_settings_{$gateway_id} |
(none) | Pro.php:1057 |
Pro Plugin Filters [PRO]
Section titled “Pro Plugin Filters [PRO]”EDD Filters
Section titled “EDD Filters”| Filter | Parameters | File |
|---|---|---|
wpss_edd_cart_item_data |
$cart_item_data, $product_id, $variation_id |
EDDCheckoutProvider.php:56 |
wpss_edd_validate_add_to_cart |
$valid, $product_id, $quantity |
EDDCheckoutProvider.php:97 |
wpss_edd_thankyou_redirect |
$redirect, $order_id |
EDDCheckoutProvider.php:251 |
wpss_edd_can_access_vendor_dashboard |
$can_access, $user_id |
src/Integrations/EDD/EDDAccountProvider.php:483 |
WooCommerce Filters
Section titled “WooCommerce Filters”| Filter | Parameters | File |
|---|
Related Documentation
Section titled “Related Documentation”- REST API Reference - API endpoints and authentication
- Custom Integrations - Building custom adapters and gateways
- Theme Integration - Template overrides and styling
Paying for a single order (sub-orders)
Section titled “Paying for a single order (sub-orders)”Tips, milestone phases and extension quotes are sub-orders: a real WPSS order of their own, created against a parent order, that the buyer pays separately. They are the one deliberate exception to the one-rail catalog rule — the catalog checkout belongs entirely to the active platform, but a sub-order has no cart and cannot go through it.
wpss_get_pay_order_url( int $order_id ): string
Section titled “wpss_get_pay_order_url( int $order_id ): string”The single source for “pay THIS order.” Every surface that links a buyer to
a payment — dashboard timeline, order view, emails, notifications — must call
it. Never build a ?pay_order= link by hand; it is correct only on one rail.
$url = wpss_get_pay_order_url( $order_id );wpss_pay_order_url_lookup and wpss_ensure_pay_order (filters)
Section titled “wpss_pay_order_url_lookup and wpss_ensure_pay_order (filters)”apply_filters( 'wpss_pay_order_url_lookup', string $url, int $order_id, ?object $order ); // readapply_filters( 'wpss_ensure_pay_order', string $url, int $order_id ); // writeA cart-based rail replaces the URL entirely. Pro’s WooCommerce implementation
(WCPayOrderResolver) creates — or reuses — a real WooCommerce order for the
sub-order in wpss_ensure_pay_order and returns its native order-pay URL, so
the link works from an email days later with no cart session. Its
wpss_pay_order_url_lookup rebuilds that URL from the id and key stored on
the WPSS row, so lists and pages never load or create a WooCommerce order.
Supported rails
Section titled “Supported rails”See Pay-order support by rail above – the one table, kept beside the filters it describes.
A platform that has not implemented wpss_ensure_pay_order gets no Pay button
at all (wpss_can_pay_single_order() is false) — so implement the filters for
any new platform the way WCPayOrderResolver does. Do not solve it by
re-enabling WPSS gateways alongside the platform’s own, which would break the
one-rail contract.
Why wpss_get_checkout_base_url() is not the answer
Section titled “Why wpss_get_checkout_base_url() is not the answer”It returns the active rail’s checkout — WooCommerce’s under Woo. Sending a
buyer there for a sub-order lands them on an empty cart, because a sub-order was
never added to one. That is the trap the filter above exists to avoid, and the
reason browser Pay never reaches StandaloneCheckoutProvider::render_pay_order_checkout()
on a cart rail.
Mobile Session and Selling Limits (1.6.0)
Section titled “Mobile Session and Selling Limits (1.6.0)”Token lifetime
Section titled “Token lifetime”| Filter | Parameters | File |
|---|---|---|
wpss_app_token_lifetime |
array{idle:int, absolute:int} $lifetime |
functions/misc.php |
wpss_token_recovery_routes |
array<int,string> $routes |
API/AppTokenGuard.php |
App tokens expire 30 days after last use or 90 days after issue, whichever comes
first. Returning 0 for either key disables that limit; disabling both restores
the pre-1.6.0 behaviour of tokens that never expire, which is what that release
was filed to end.
// A 7-day idle window for a high-security marketplace.add_filter( 'wpss_app_token_lifetime', function ( array $lifetime ): array { $lifetime['idle'] = 7 * DAY_IN_SECONDS; return $lifetime;} );wpss_token_recovery_routes lists the routes reachable with an expired token
in the Authorization header - by default /auth/login, /auth/register and
/auth/forgot-password.
Do not add a route that reads the current user. WordPress 401s the whole request on a failed application password, so without this carve-out an app that attaches its stored token to every request could never reach the login route to replace it. These three take their credentials from the request body and grant nothing on their own, which is what makes them safe to open.
Sign-in lockout (1.7.1)
Section titled “Sign-in lockout (1.7.1)”| Filter | Parameters | File |
|---|---|---|
wpss_web_login_lock |
bool $enabled |
Core/Plugin.php |
Five wrong passwords for one account inside 15 minutes lock it for 15 minutes,
on the website sign-in form and on POST /auth/login alike - one counter, keyed
on the resolved account rather than on the address the attempts came from. A
successful sign-in clears it, and the 15 minutes always runs down, so an
administrator cannot be held out indefinitely.
Return false to leave wp-login.php alone on a site whose security plugin
already limits failed sign-ins. The counter keeps running either way, so the
app’s own lockout - 423 wpss_account_locked - is unaffected.
// A dedicated security plugin owns the login form on this site.add_filter( 'wpss_web_login_lock', '__return_false' );Selling limits
Section titled “Selling limits”| Filter | Parameters | File |
|---|---|---|
wpss_member_bypasses_limits |
bool $bypasses, int $user_id |
functions/vendors.php |
wpss_vendor_can_create_service |
bool $can_create, int $vendor_id |
two gates, see below |
Administrators bypass vendor selling limits by default, so a site owner seeding demo content or building services for a client does not meet their own paywall.
// Meter administrators too.add_filter( 'wpss_member_bypasses_limits', '__return_false' );wpss_vendor_can_create_service has TWO gates on it, and this catches
people out: this plugin enforces a per-profile maximum at priority 10, and Pro’s
plan enforcer runs at priority 20. If you are testing whether a member may
create a service, run the filter - calling either class directly can return
true while the filter answers false.
Presence and Messaging (1.6.0)
Section titled “Presence and Messaging (1.6.0)”| Filter | Parameters | File |
|---|---|---|
wpss_skip_message_email_when_online |
bool $skip, int $recipient_id, bool $enabled |
functions/notifications.php |
wpss_presence_window |
int $seconds |
functions/notifications.php |
wpss_messages_per_page |
int $per_page |
templates/dashboard/sections/messages.php |
There is no settings screen for the presence behaviour - it is on by default and adjusted here.
Gallery and Layout (1.6.0)
Section titled “Gallery and Layout (1.6.0)”| Filter | Parameters | File |
|---|---|---|
wpss_video_thumbnail_cache_ttl |
int $ttl, string $video_url |
functions/services.php |
wpss_gallery_image_size |
string $size, int $service_id |
partials/service-gallery.php |
wpss_sticky_top_offset |
int $offset |
Frontend/Frontend.php |
A video thumb uses the embed provider’s own poster frame, fetched through oEmbed and cached for a week. That call is an HTTP request to the provider, so if you shorten the TTL, shorten it deliberately - an uncached lookup puts a third-party round trip in front of every visitor.
Use wpss_sticky_top_offset when a theme has its own sticky header that the
plugin’s measurement cannot see.
Categories (1.6.0)
Section titled “Categories (1.6.0)”| Filter | Parameters | File |
|---|---|---|
wpss_category_terms_limit |
int $limit |
functions/services.php |
Category choosers cap at 200 terms. The helper wpss_group_category_terms()
turns a flat term list into parents each carrying their children, and is what
every single-dropdown chooser in the plugin uses - reuse it rather than grouping
by hand, or the two will drift as they did before 1.6.0.
Orphans - a child whose parent is missing because hide_empty dropped it - are
promoted to top level rather than discarded, so a category with services in it
always stays reachable.

