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.