infobip/infobip-api-php-client
Official PHP client for the Infobip API, giving Laravel and PHP apps PSR-compatible access to SMS, WhatsApp, email, voice and 2FA messaging, with request builders, models and error handling for fast omnichannel integration.
Install with Composer and pin a major version, because this is an OpenAPI-generated client and the generated class/method names change between releases:
composer require infobip/infobip-api-php-client:^5.0
You need two things from the Infobip dashboard before writing code: the base URL for your account/region (e.g. https://xxxxx.api.infobip.com — it is not a global URL) and an API key. Put both in environment variables, not in source.
Where to look first:
README.md and the examples/ directory in the package — the fastest sanity check.src/Api/ — one class per Infobip product (SmsApi, TfaApi, EmailApi, WhatsAppApi, …). The method names map closely to the REST endpoints in Infobip's public API docs, so use the REST docs as your real reference and match the endpoint to the generated method.src/Model/ — the request/response DTOs. Constructors are long and use named arguments; read the constructor signature rather than guessing.Minimal first use case — send one SMS and confirm credentials work:
use Infobip\Configuration;
use Infobip\Api\SmsApi;
use Infobip\Model\SmsRequest;
use Infobip\Model\SmsMessage;
use Infobip\Model\SmsDestination;
use Infobip\Model\SmsTextContent;
$config = new Configuration(
host: rtrim(config('services.infobip.host'), '/'),
apiKey: config('services.infobip.key'),
);
$smsApi = new SmsApi(config: $config);
$response = $smsApi->sendSmsMessages(new SmsRequest(
messages: [
new SmsMessage(
destinations: [new SmsDestination(to: '+15551234567')],
sender: 'MySender',
content: new SmsTextContent(text: 'Hello from Infobip'),
),
],
));
foreach ($response->getMessages() as $message) {
logger()->info('infobip', [
'id' => $message->getMessageId(),
'status' => $message->getStatus()->getName(),
]);
}
If that returns a SmsResponse with a message ID, your host, key, and sender are all correct. From there, expand to the product API you actually need (2FA, email, WhatsApp) using the same pattern.
Wrap the client in a service class bound in the container. Do not construct Configuration or Api objects inside controllers; create one configured instance per process and inject it. In Laravel that looks like a singleton binding in a service provider plus a config/services.php entry for host/key.
Separate the "build request" step from the "call API" step. The generated models are verbose, so it pays to have a small factory that turns your domain payload (e.g. an SmsNotification object) into SmsRequest/SmsMessage/SmsDestination models. Your application code then depends on your own classes, not on generated ones, which makes the next major-version bump much less painful.
Sending patterns you will actually use:
SmsMessage with many SmsDestination entries is cheaper than many messages, and is the normal way to send to a list.SmsDestination can carry a messageId or placeholder values for templated content; use that instead of string-replacing text client-side.SmsRequest exposes a sendAt field — use it rather than a cron job that sleeps.notifyUrl on the request to receive per-message delivery callbacks. The initial HTTP response only means "accepted", not "delivered".Queue anything bulk. Sending to hundreds of recipients is a multi-second network call and is rate-limited. Dispatch a queued job per batch, with retries and backoff, rather than doing it in a request/response cycle.
Testing: the generated API classes accept a PSR-18 HTTP client, so you can inject a mock (Guzzle's MockHandler) and assert on serialized request bodies. More practically, fake at your own service-class boundary and reserve real HTTP mocks for the request-building factory.
2FA flow (TFA is a distinct API class): send PIN → verify PIN → optionally resend. Persist the pinId from the send response; you need it to verify. Treat verification failures as normal results (they are in the response body), not exceptions.
Webhooks: Infobip retries delivery reports. Make the endpoint idempotent (key on message ID), acknowledge with 200 immediately, and push processing onto a queue. There is no HMAC signature to verify, so restrict the endpoint by IP allowlist or a secret path segment if you can.
rtrim() it. Never derive it from the API key.App <apiKey>, which the Configuration object sets for you. If you ever hand-roll requests, don't send a bearer token.from/to or sender/destination is the most common bug here.status.groupId/status.name (PENDING, SENT, REJECTED, …). A 200 response can still contain rejected messages; check every entry, not just getMessages()[0].Infobip\ApiException. Catch it explicitly, log getCode() and the response body — it contains Infobip's requestError.serviceException detail (e.g. invalid sender, insufficient balance), which is much more useful than the exception message. Don't let a 4xx bubble up as a 500 in your app.+15551234567, no spaces or dashes). Sender IDs must be pre-approved for the destination country; you'll only find out at send time otherwise.nulls for unset optional fields. If the API rejects a payload, dump the actual JSON body from your logger before assuming a model is wrong.How can I help you explore Laravel packages today?