= 500; if ( $retryable && $attempt < self::MAX_ATTEMPTS ) { self::schedule( $event, $attempt + 1, (int) min( 3600, 30 * ( 2 ** ( $attempt - 1 ) ) ) ); self::log( $event, 'retrying', $result['status'] ); return; } self::log( $event, 'failed', $result['status'] ); } /** * Sends one signed request. * * @return array{ok: bool, status: int, body: string} */ public static function post( array $event ): array { $url = RootCue_Settings::webhook_url(); $secret = RootCue_Settings::secret(); if ( '' === $url || '' === $secret ) { return array( 'ok' => false, 'status' => 0, 'body' => 'not configured' ); } $body = wp_json_encode( $event ); $response = wp_remote_post( $url, array( 'timeout' => 10, 'redirection' => 0, 'headers' => array( 'Content-Type' => 'application/json', 'X-RootCue-Signature' => RootCue_Payload::sign( $secret, $body, time() ), 'User-Agent' => 'RootCue-WordPress/' . ROOTCUE_VERSION, ), 'body' => $body, ) ); if ( is_wp_error( $response ) ) { return array( 'ok' => false, 'status' => 0, 'body' => $response->get_error_message() ); } $status = (int) wp_remote_retrieve_response_code( $response ); return array( 'ok' => $status >= 200 && $status < 300, 'status' => $status, 'body' => (string) wp_remote_retrieve_body( $response ) ); } /** Keeps the last 25 deliveries — event IDs and types only, never customer details. */ private static function log( array $event, string $outcome, int $status ): void { $log = get_option( self::LOG_OPTION, array() ); if ( ! is_array( $log ) ) { $log = array(); } array_unshift( $log, array( 'at' => time(), 'type' => (string) ( $event['type'] ?? '' ), 'id' => (string) ( $event['id'] ?? '' ), 'outcome' => $outcome, 'status' => $status, ) ); update_option( self::LOG_OPTION, array_slice( $log, 0, 25 ), false ); } }