Payment gateways, SMS providers, shipping companies and AI services all work the same way from PHP: your code sends an HTTPS request, the service sends back JSON. Getting the request to work is easy. Making it safe when the other service is slow, down or returns an error is what separates a reliable integration from one that takes your website down with it.
Use cURL or Guzzle, and give every call a connect timeout and a total timeout of a few seconds: a call without one can wait forever and tie up your site. Check the HTTP status code, decode JSON with exceptions turned on, and log failures. Keep API keys in a config file or environment variable outside the web root, never in your code or Git. Keep TLS certificate checks on, retry only safe requests, and verify webhook signatures.
1. How an API call works
A REST API is a set of URLs, called endpoints, that accept standard HTTP methods: GET to read, POST to create, PUT or PATCH to update, DELETE to remove. You send headers (usually an API key or token) and sometimes a JSON body; the service replies with a status code and a JSON body. Some older services use SOAP instead, which PHP handles with SoapClient.
Read the provider's documentation first for four things: the base URL, how to authenticate, the rate limits, and the error format.
2. Keep secrets out of your code
An API key in a PHP file ends up in Git, in backups and in every copy of the site. Store it outside the web root instead.
/home/youruser/app/secrets.php (permissions 600, next to public_html, not inside it):
<?php
return [
'weather_api_key' => 'your-key-here',
'payments_key_id' => 'rzp_live_xxx',
'payments_secret' => 'your-secret-here',
];$secrets = require '/home/youruser/app/secrets.php';
$apiKey = $secrets['weather_api_key'];Frameworks use a .env file for the same job, and on a VPS or container platform you can use environment variables (getenv('WEATHER_API_KEY')). Whichever you use:
- never commit the file; add it to
.gitignore; - use separate test and live keys;
- give each key only the permissions it needs, and rotate it if it ever leaks.
3. A safe cURL request
This helper handles the things most examples leave out: timeouts, status codes, JSON errors and certificate checks.
<?php
declare(strict_types=1);
final class ApiException extends RuntimeException {}
function api_request(string $method, string $url, array $headers = [], ?array $json = null): array
{
$ch = curl_init($url);
$headers[] = 'Accept: application/json';
if ($json !== null) {
$headers[] = 'Content-Type: application/json';
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($json, JSON_THROW_ON_ERROR));
}
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5, // seconds to open the connection
CURLOPT_TIMEOUT => 15, // seconds for the whole request
CURLOPT_FOLLOWLOCATION => false,
// Certificate checks are on by default. Never set VERIFYPEER to false.
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false) {
throw new ApiException('Network error: ' . curl_error($ch));
}
if ($status >= 400) {
throw new ApiException("API returned HTTP $status: " . substr($body, 0, 300), $status);
}
return $body === '' ? [] : json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}Using it:
$secrets = require '/home/youruser/app/secrets.php';
try {
$weather = api_request(
'GET',
'https://api.example.com/v1/weather?' . http_build_query(['city' => 'Mumbai']),
['Authorization: Bearer ' . $secrets['weather_api_key']]
);
echo htmlspecialchars($weather['summary'] ?? 'No data', ENT_QUOTES, 'UTF-8');
} catch (ApiException | JsonException $e) {
error_log('Weather API failed: ' . $e->getMessage());
echo 'Weather is unavailable right now.';
}Notes:
http_build_query()encodes query values safely; never paste user input straight into a URL.- Escape API data before printing it. A third-party response is untrusted input.
curl_close()is not needed in PHP 8; the handle is freed automatically.
Without CURLOPT_TIMEOUT, cURL waits as long as the other server takes, and a timeout of 0 means "wait forever". When the API slows down, every page that calls it hangs and holds one of your account's PHP slots. On shared hosting the slots fill up and visitors get a 508 "Resource Limit Is Reached" page, even though your CPU use is low. We have seen exactly this with payment-status checks. See Understanding the Resource Limit Is Reached error.
4. The same thing with Guzzle
Guzzle is the most widely used PHP HTTP client. Install it with composer require guzzlehttp/guzzle (on shared hosting, run Composer on your own computer and upload the vendor folder).
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
$client = new Client([
'base_uri' => 'https://api.example.com/v1/',
'connect_timeout' => 5,
'timeout' => 15,
'headers' => ['Accept' => 'application/json'],
]);
try {
$res = $client->get('weather', [
'query' => ['city' => 'Mumbai'],
'headers' => ['Authorization' => 'Bearer ' . $secrets['weather_api_key']],
]);
$data = json_decode((string) $res->getBody(), true, 512, JSON_THROW_ON_ERROR);
} catch (GuzzleException | JsonException $e) {
error_log('Weather API failed: ' . $e->getMessage());
}Guzzle throws an exception for 4xx and 5xx responses by default, so errors cannot slip through unnoticed. Symfony HttpClient is a good alternative, and Laravel has its own Http client built on Guzzle, which takes ->timeout() and ->connectTimeout().
5. Authentication patterns
| Method | How you send it | Typical use |
|---|---|---|
| API key | A header such as Authorization: Bearer KEY or X-API-Key: KEY | Weather, maps, SMS, AI APIs |
| Basic auth | CURLOPT_USERPWD with key ID and secret | Many payment gateways |
| OAuth 2.0 client credentials | Exchange ID and secret for a short-lived token, then send the token | PayPal, Google and other large platforms |
| OAuth 2.0 authorisation code | The user signs in at the provider and approves access | "Log in with Google", access to a user's data |
Prefer headers to query-string parameters for keys: URLs end up in server logs. For OAuth tokens, cache the token until shortly before it expires instead of requesting a new one on every page load.
6. Errors, retries and rate limits
- Read the status code.
400means your request is wrong;401or403means a bad or under-privileged key;404a wrong endpoint;429too many requests;5xxa problem at the provider. - Retry only when it can help: on network errors,
429and5xx, at most two or three times, waiting longer each time (for example 1, 2 then 4 seconds), and honour aRetry-Afterheader if the API sends one. - Never blindly retry a payment or order. If the first request reached the provider before the connection dropped, a retry can charge twice. Use the provider's idempotency key if it offers one, or check the order status before trying again.
- Log enough to debug, such as the endpoint, status code and the provider's error message, but never log keys, card data or full personal records.
- Cache what does not change often. Exchange rates, weather and product lists can be stored for a few minutes in a file or database table, which saves your rate limit and makes pages faster.
7. Receiving webhooks
Many APIs call you back, for example when a payment succeeds. Treat that incoming request as untrusted until you have verified its signature:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $payload, $secrets['webhook_secret']);
if (!hash_equals($expected, $signature)) {
http_response_code(400);
exit;
}
$event = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);
// Record the event ID and ignore duplicates, then respond quickly with 200.Header names and signing methods differ between providers, so follow their documentation exactly, and reply quickly: providers retry webhooks that time out.
8. Running API integrations on Domain India
On our cPanel shared servers, curl_exec() works, so the cURL helper and Guzzle run as they are. curl_multi_exec() is disabled, so parallel requests are not available; Guzzle notices this and sends requests one at a time. Raw socket functions such as fsockopen() are disabled too, which is why some older SDKs fail. See PHP disabled functions on shared hosting.
On our DirectAdmin shared server, curl_exec() and curl_multi_exec() are disabled for websites by default (measured 23 September 2026), as a protection against outbound abuse. allow_url_fopen is on, so Guzzle and Symfony HttpClient fall back to PHP's own HTTPS streams and keep working, with the same timeouts. If your application or SDK needs cURL itself, open a ticket and tell us the domain and what it needs.
For Webuzo, ask support. If an integration needs background workers, long-running connections or its own libraries, a VPS gives you full control, and the App Platform runs PHP apps from a Dockerfile. The card shows the live monthly price, excluding 18% GST.
- 25 GB NVMe SSD Storage
- 50 GB Monthly Bandwidth
- 1 Website
- 10 Email Accounts
What timeout should I set for API calls in PHP?
Set a connect timeout of about 5 seconds and a total timeout of about 10 to 30 seconds, depending on the API. In cURL use CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT; in Guzzle use connect_timeout and timeout. A value of 0 means wait forever and should never be used on a website.
Where should I store API keys in a PHP application?
In a config file outside public_html with permissions set to 600, a framework .env file, or environment variables. Never hard-code keys in files inside the web root or commit them to Git, and use separate test and live keys.
Is it safe to set CURLOPT_SSL_VERIFYPEER to false?
No. It turns off certificate checking, so anyone between your server and the API can read or change the traffic, including your API key. If a certificate error appears, fix the cause instead.
Why does my site show Resource Limit Is Reached when an API is slow?
Each page waiting on the API holds one of your account's PHP slots. With no timeout, the waits pile up until every slot is busy and visitors get a 508 page. Add a short timeout to every outbound call and cache results where you can.
Why does cURL fail on my DirectAdmin hosting?
On Domain India's DirectAdmin shared server, curl_exec and curl_multi_exec are disabled for websites by default. Use Guzzle or Symfony HttpClient, which fall back to PHP's HTTPS streams, or open a ticket if your software needs cURL itself.
Ready to connect your first API? Check PHP disabled functions on shared hosting before you choose an SDK, compare cPanel and DirectAdmin hosting, or open a ticket if a call fails on your hosting and you are not sure why.
A choice of PHP versions per domain, cURL for outbound HTTPS, and free SSL on every plan.
See cPanel plans