get_header( 'x_templately_signature' ); if ( empty( $signature ) ) { $signature = $request->get_header( 'X-Templately-Signature' ); } $timestamp = $request->get_header( 'x_templately_timestamp' ); if ( empty( $timestamp ) ) { $timestamp = $request->get_header( 'X-Templately-Timestamp' ); } $result = self::verify( $request->get_params(), $signature, $timestamp, $api_key ); if ( true === $result ) { return true; } // verify() returned a WP_Error. if ( self::is_enforced() ) { return $result; } // Log-only grace window: record the failure, allow the request through. Helper::log( [ 'context' => $context, 'code' => is_wp_error( $result ) ? $result->get_error_code() : 'unknown', 'message' => is_wp_error( $result ) ? $result->get_error_message() : '', ], 'callback_signature_unverified' ); return true; } /** * Check if timestamp is within acceptable tolerance. * * @param int $timestamp Unix timestamp to check * @param int $tolerance Tolerance in seconds * @return bool True if timestamp is valid */ private static function is_timestamp_valid($timestamp, $tolerance) { $current_time = time(); $time_difference = abs($current_time - $timestamp); return $time_difference <= $tolerance; } /** * Generate HMAC-SHA256 signature for payload. * * @param array $payload Request payload * @param int $timestamp Unix timestamp * @param string $api_key User's API key (used as secret) * @return string HMAC signature */ private static function generate_signature($payload, $timestamp, $api_key) { $canonical_string = self::create_canonical_string($payload, $timestamp); return hash_hmac('sha256', $canonical_string, $api_key); } /** * Create canonical string from payload and timestamp. * * Only includes security-critical fields in signature to avoid * performance issues with large template content. * * @param array $payload Request payload * @param int $timestamp Unix timestamp * @return string Canonical string */ private static function create_canonical_string($payload, $timestamp) { // Extract only security-critical fields for signature // Exclude large content fields like 'template' and 'error' // Also exclude 'isSkipped' as requested $signature_fields = [ 'process_id' => isset($payload['process_id']) ? $payload['process_id'] : null, 'content_id' => isset($payload['content_id']) ? $payload['content_id'] : null, 'template_id' => isset($payload['template_id']) ? $payload['template_id'] : null, 'type' => isset($payload['type']) ? $payload['type'] : null, ]; // Remove null values $signature_fields = array_filter($signature_fields, function ($value) { return $value !== null; }); // Sort keys for consistency ksort($signature_fields); // JSON encode with consistent flags $payload_json = wp_json_encode($signature_fields, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); // Combine timestamp and payload return $timestamp . '.' . $payload_json; } }