Entity-level webhooks for WordPress using Action Scheduler. Sends non-blocking POSTs for create/update/delete of posts, terms, and users. Meta changes trigger the entity update. ACF updates include field context. Features intelligent failure monitoring with email notifications, automatic blocking, and comprehensive filtering options.
- Action Scheduler dispatch - 5s delay, non-blocking async delivery
- Automatic deduplication - Dispatcher-level on action+entity+id and meta-level per-request dedup for plugin hooks (ACF, Meta Box, etc.)
- Entity-aware payloads - Includes post_type, taxonomy, or roles
- ACF integration - Adds field key/name context to meta changes
- Registry pattern - Extensible webhook system with custom configurations
- Notification system - Opt-in email alerts for failures, extensible notification handlers
- Failure monitoring - Automatic blocking after consecutive failures
- Comprehensive filtering - Control payloads, URLs, headers, and meta keys
composer require citation-media/wp-webhook-frameworkRequires PHP 8.1+.
Ensure Action Scheduler is active (dependency is declared).
// Initialize the framework
\Citation\WP_Webhook_Framework\Service_Provider::register();See Configuration for detailed configuration options.
No webhooks are active by default. Register what you need via the action:
use Citation\WP_Webhook_Framework\Webhooks\Post_Webhook;
add_action('wpwf_register_webhooks', function ($registry) {
$post = new Post_Webhook();
$post->webhook_url('https://api.example.com/posts')
->max_retries(3)
->max_consecutive_failures(5)
->timeout(60)
->notifications(['blocked']);
$registry->register($post);
});Multiple plugins can each register their own webhook instance for the same entity type. Each instance gets independent URL, retry policy, timeout, and failure tracking:
add_action('wpwf_register_webhooks', function ($registry) {
$analytics = new \Citation\WP_Webhook_Framework\Webhooks\Post_Webhook('post_analytics');
$analytics->webhook_url('https://analytics.example.com/posts')
->timeout(10);
$registry->register($analytics);
$crm = new \Citation\WP_Webhook_Framework\Webhooks\Post_Webhook('post_crm');
$crm->webhook_url('https://crm.example.com/webhook')
->max_retries(5)
->timeout(60);
$registry->register($crm);
});class Custom_Webhook extends \Citation\WP_Webhook_Framework\Webhook {
public function __construct() {
parent::__construct('my_custom_webhook');
$this->max_retries(3)
->max_consecutive_failures(3)
->timeout(30)
->webhook_url('https://api.example.com/custom')
->headers(['Authorization' => 'Bearer token123']);
}
public function init(): void {
add_action('my_custom_action', [$this, 'handle_action'], 10, 1);
}
public function handle_action($data): void {
$payload = ['custom_data' => $data, 'timestamp' => time()];
$this->emit('action_triggered', 'custom', $data['id'], $payload);
}
}
// Register the webhook
add_action('wpwf_register_webhooks', function($registry) {
$registry->register(new Custom_Webhook());
});See Custom Webhooks for detailed webhook creation guide.
// Prevent delete webhooks
add_filter('wpwf_payload', function($payload, $entity, $id) {
if ($payload['action'] === 'delete') {
return []; // Return empty array to prevent webhook
}
return $payload;
}, 10, 3);
// Add custom data to user webhooks
add_filter('wpwf_payload', function($payload, $entity, $id) {
if ($entity === 'user') {
$user = get_userdata($id);
$payload['email'] = $user->user_email;
}
return $payload;
}, 10, 3);See Hooks and Filters for all available hooks and filters.
Important: Webhook instances are singletons and must remain stateless. Never store per-emission data as instance properties.
Stateless (configuration):
// Set once during __construct() or via registry
$this->webhook_url('https://api.example.com')
->max_consecutive_failures(5);Stateful (emission data):
// Pass directly to emit() - NEVER store on instance
$this->emit('update', 'post', $post_id, $payload);See Webhook Statefulness for detailed explanation.
- Service_Provider - Singleton that bootstraps the framework
- Webhook_Registry - Manages webhook instances and initialization
- Webhook (abstract) - Base class for all webhooks
- Dispatcher - Schedules and sends HTTP requests via Action Scheduler
- Entity Handlers - Prepare payloads for posts, terms, users, and meta
Data Flow:
WordPress hook → Webhook → Handler::prepare_payload() → Webhook::emit() → Dispatcher → Action Scheduler → HTTP POST
Example webhook payload:
{
"action": "update",
"entity": "post",
"id": 123,
"post_type": "post"
}Entity-specific invariants:
- Post:
post_type - Term:
taxonomy - User:
roles[] - Meta:
acf_field_key,acf_field_name(if ACF field)
- Email notifications on first failure after retries exhausted
- Automatic blocking after 10 consecutive failures within 1 hour
- Auto-unblock after 1 hour
- Success resets failure count and blocked status
See Failure Handling for configuration and customization.
- PHPCS Compliant - WordPress coding standards (WPCS 3.1)
- Type Safe - PHPStan level 6 static analysis
- i18n Ready - All user-facing strings internationalized
- Filterable - Extensive WordPress filter integration
- Well Documented - Comprehensive inline documentation
composer install # Install dependencies
composer run-script phpstan # Run static analysis
composer run-script phpcs # Lint code
composer run-script phpcbf # Auto-fix code style