WordPress HTTP API: wp_remote_get and wp_remote_post Best Practices

WordPress’s HTTP API (wp_remote_get(), wp_remote_post()) wraps the underlying transport layer — cURL or PHP streams — and provides a consistent interface for outbound HTTP requests. Using it correctly means handling errors, setting timeouts, caching responses, and avoiding N+1 API calls on page load.

Problem: WordPress's wp_remote_get() and wp_remote_post() functions are used without understanding their default timeout, redirect handling, and SSL verification behaviour — leading to silent failures, security issues, or hung requests in production.

Solution: Always pass explicit timeout, sslverify, and user-agent args. Check for WP_Error with is_wp_error() before accessing the response. Use wp_remote_retrieve_response_code() to validate the HTTP status, and set blocking => false for fire-and-forget requests. Never disable SSL verification in production.

The examples below fetch and cache an external API response, make authenticated POST requests, handle HTTP errors properly, and use the http_api_args filter to set default options globally.

 10,              // seconds; default is 5
            'user-agent' => 'MyPlugin/1.0 (https://example.com)',
            'headers'    => [
                'Accept'        => 'application/vnd.github.v3+json',
                'Authorization' => 'Bearer ' . get_option( 'myplugin_github_token' ),
            ],
        ]
    );

    // Always check for WP_Error first (network failure, DNS, timeout)
    if ( is_wp_error( $response ) ) {
        return $response;
    }

    $status = wp_remote_retrieve_response_code( $response );
    if ( $status !== 200 ) {
        return new WP_Error(
            'github_api_error',
            sprintf( 'GitHub API returned HTTP %d', $status ),
            [ 'status' => $status ]
        );
    }

    $body = wp_remote_retrieve_body( $response );
    $data = json_decode( $body, true );

    if ( json_last_error() !== JSON_ERROR_NONE ) {
        return new WP_Error( 'json_parse_error', 'Invalid JSON response' );
    }

    // Cache for 1 hour — respect GitHub's rate limiting
    set_transient( $cache_key, $data, HOUR_IN_SECONDS );
    return $data;
}

// ── POST REQUEST WITH JSON BODY ──
function myplugin_send_webhook( string $url, array $payload ): bool|WP_Error {
    $response = wp_remote_post( $url, [
        'timeout'     => 15,
        'headers'     => [ 'Content-Type' => 'application/json' ],
        'body'        => wp_json_encode( $payload ),
        'data_format' => 'body',  // send body as raw string, not form-encoded
    ] );

    if ( is_wp_error( $response ) ) return $response;

    $code = wp_remote_retrieve_response_code( $response );
    return $code >= 200 && $code < 300;
}

Block external HTTP requests in local dev and set global defaults:

 15 );

// Log all outbound requests for debugging
add_action( 'http_api_debug', function( $response, string $context, string $class, array $args, string $url ) {
    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
        $code = is_wp_error( $response ) ? $response->get_error_code() : wp_remote_retrieve_response_code( $response );
        error_log( sprintf( '[HTTP] %s %s → %s', strtoupper( $args['method'] ?? 'GET' ), $url, $code ) );
    }
}, 10, 5 );

// Parallel requests using wp_remote_get with pre-seeded cache
// For truly parallel requests, use curl_multi via a custom transport or Guzzle
// The built-in HTTP API is sequential — cache aggressively to avoid serial waterfalls
function myplugin_prefetch_api_data( array $urls ): void {
    foreach ( $urls as $url ) {
        $key = 'prefetch_' . md5( $url );
        if ( false === get_transient( $key ) ) {
            $resp = wp_remote_get( $url, [ 'timeout' => 5 ] );
            if ( ! is_wp_error( $resp ) ) {
                set_transient( $key, wp_remote_retrieve_body( $resp ), 10 * MINUTE_IN_SECONDS );
            }
        }
    }
}

NOTE: Never call wp_remote_get() directly inside the main query loop or in a wp_head action — it makes synchronous HTTP calls that block page rendering. Always cache responses in transients and trigger external API calls from background processes (WP-Cron or Action Scheduler) rather than request-time code.

Leave Comment

Your email address will not be published. Required fields are marked *