registerWebhook($event); } elseif (!$new && $old) { foreach ($events as $event) $this->deregisterWebhook($event); } }; add_action('added_option', fn ($name, $value) => $handler($name, $value), 10, 2); add_action('updated_option', fn ($name, $old, $new) => $handler($name, $new, $old), 10, 3); } // Order a webhook from Aikanen to get notified on specific event private function registerWebhook(string $event): void { $secret = get_option('aikanen_webhook_secret'); if (!$secret) return; $url = rest_url("aikanen/v1/webhook/{$this->key()}"); try { $response = $this->api->post('/webhooks', [ 'event' => $event, 'url' => $url, 'secret' => $secret, ]); $id = $response['data']['id'] ?? null; if ($id) { update_option("aikanen_webhook_id_{$this->key()}_{$event}", $id); } } catch (\Exception $e) { set_transient('aikanen_notice', [ 'type' => 'error', 'message' => sprintf( /* translators: %s: error message */ __('Failed to register webhook: %s', 'aikanen'), $e->getMessage() ), ], 30); } } // Delete a webhook from Aikanen to stop getting notified on specific event private function deregisterWebhook(string $event): void { $id = get_option("aikanen_webhook_id_{$this->key()}_{$event}"); if (!$id) return; try { $this->api->delete("/webhooks/{$id}"); delete_option("aikanen_webhook_id_{$this->key()}_{$event}"); } catch (\Exception $e) { set_transient('aikanen_notice', [ 'type' => 'error', 'message' => sprintf( /* translators: %s: error message */ __('Failed to deregister webhook: %s', 'aikanen'), $e->getMessage() ), ], 30); } } // Verify signature of received webhook matches expected (calculated from saved secret and webhook data) public function verifyWebhookSignature(\WP_REST_Request $request): bool { $secret = get_option('aikanen_webhook_secret'); if (!$secret) { // No secret saved to Woo return false; } $signature = $request->get_header('signature'); if (!$signature) { // No signature in received webhook return false; } return hash_equals( hash_hmac('sha256', $request->get_body(), $secret), $signature ); } }