Main Header

Add a bundled service

Updated on July 30, 2026

Bundled services live inside the SignalPress or SignalPress Pro plugin under services/{service-id}/. SignalPress discovers them automatically — no bootstrap edit required.

Overview: Extending SignalPress. External plugins: Custom integrations.

Directory layout

Text

services/
  my-service/
    service.php              ← entry file (required)
    class-signalpress-my-service-service.php
    logo.svg
    README.md                ← service Help tab (recommended)

Reference implementation: services/local/ in the free plugin.

Entry file

service.php must return an object implementing SignalPress_Provider (prefer SignalPress_Service):

PHP





Free plugin discovery

SignalPress_Service_Loader globs:

Text

signalpress/services/*/service.php

Each valid object is registered with register_internal() -- reserved for official SignalPress packages.

Pro plugin discovery

Pro registers on _signalpress/register_internal_services:

  1. Prefer class SignalPress_{Service_Id}_Service if it exists (for example SignalPress_Posthog_Service for services/posthog/).
  2. Otherwise require service.php and use the returned instance.

Pro services also use register_internal().

Implement SignalPress_Service

Minimum methods (see services/local/class-signalpress-local-service.php):

MethodPurpose
get_id()Stable service ID (lowercase, underscores) -- used in Service Targeting
get_name()Admin display name
get_description()Integrations card copy
is_premium()false for free-tier services; true when license gating applies
get_logo_url()Integrations card icon
get_service_url() / get_account_url()External links (optional)
get_settings_fields()Settings form schema
is_configured()Whether delivery can run
capture()Send the event; return true or WP_Error

Optional helpers

MethodPurpose
sanitize_settings()Normalize saved settings on Settings save
can_test_connection() / test_connection()Connection test button on Settings tab
get_required_license_feature()Hub feature flag when is_premium() is true
enqueue_browser_tracking()Load a browser SDK when enabled

capture() contract

PHP

public function capture( SignalPress_Event $event, array $settings ) {
	$payload = [
		'event'      => $event->get_name(),
		'properties' => $event->get_properties(),
		'context'    => $event->get_context(),
	];

	$response = wp_remote_post(
		'https://api.example.com/events',
		[
			'timeout' => 5,
			'blocking' => false,
			'headers'  => [ 'Content-Type' => 'application/json' ],
			'body'     => wp_json_encode( $payload ),
		]
	);

	return is_wp_error( $response ) ? $response : true;
}

Use wp_remote_post() with 'blocking' => false for fire-and-forget delivery unless you need a synchronous test. Connection tests use blocking requests in test_connection().

Expose a service-specific payload filter when integrators need last-mile shaping -- for example signalpress/posthog/payload. Document it in the service README.

Settings fields

Common field keys:

KeyTypeNotes
label--Field label
typetext, password, url, checkbox, number, select, tagsRendered on Settings tab
default--Default value
description--Help text under the control
advanced_grouproutingPlaces field on Routing tab (shared routing fields)
premium_featurefeature idLocks field below required tier
help_sectionslugIn-plugin ? link to Help heading

Shared routing fields are injected via signalpress_routing_settings_fields() -- Advanced Event Routing.

Service Help README

Add services/{id}/README.md with setup, examples, limits, and troubleshooting. SignalPress renders it on SignalPress → {Service} → Help using the bundled Marked parser.

Tier 2: extend discovery (custom distributions)

Official packages use fixed paths. Tier 2 sites can append entry files:

PHP

add_filter(
	'signalpress/bundled_service_files',
	function ( $files ) {
		$files[] = WP_PLUGIN_DIR . '/my-agency-pack/services/acme/service.php';
		return $files;
	}
);

Returned services still register through register_internal() -- use this for private forks or agency packs, not third-party customer plugins. Customer-facing plugins should use Custom integrations.

Do not use register_internal() in third-party code

register_internal() is the package boundary for SignalPress and Pro. Third-party WordPress plugins must call $registry->register() on signalpress/register_services.