'POST', 'callback' => array( __CLASS__, 'receive' ), // Anyone may call it; the signature is the check. 'permission_callback' => '__return_true', ) ); } public static function receive( WP_REST_Request $request ) { $connection = Connection::get(); $secret = $connection && ! empty( $connection['webhook_secret'] ) ? $connection['webhook_secret'] : null; $id = (string) $request->get_header( 'webhook_id' ); $timestamp = (string) $request->get_header( 'webhook_timestamp' ); $signatures = (string) $request->get_header( 'webhook_signature' ); $body = $request->get_body(); if ( ! $secret || ! self::verify( $secret, $id, $timestamp, $body, $signatures ) ) { return new WP_REST_Response( array( 'error' => 'invalid signature' ), 401 ); } // At least once: a message seen in the last day changes nothing. $seen = self::SEEN . md5( $id ); if ( get_transient( $seen ) ) { return new WP_REST_Response( array( 'received' => true, 'repeat' => true ), 200 ); } set_transient( $seen, 1, DAY_IN_SECONDS ); $message = json_decode( $body, true ); $video = is_array( $message ) && isset( $message['data']['video']['id'] ) ? $message['data']['video'] : null; if ( $video ) { $post_id = Videos::post_of( (string) $video['id'] ); if ( $post_id ) { Videos::apply( $post_id, $video ); if ( 'completed' === $video['status'] ) { spawn_cron(); } } } return new WP_REST_Response( array( 'received' => true ), 200 ); } /** Standard Webhooks: any "v1," of the header matches, within five minutes. */ public static function verify( $secret, $id, $timestamp, $body, $signatures ) { if ( '' === $id || ! ctype_digit( $timestamp ) || abs( time() - (int) $timestamp ) > self::TOLERANCE ) { return false; } $key = base64_decode( preg_replace( '/^whsec_/', '', $secret ), true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $expected = 'v1,' . base64_encode( hash_hmac( 'sha256', $id . '.' . $timestamp . '.' . $body, (string) $key, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode foreach ( preg_split( '/\s+/', trim( $signatures ) ) as $signature ) { if ( hash_equals( $expected, $signature ) ) { return true; } } return false; } }