PHP
Envoyez des SMS depuis PHP ou Laravel via l’API REST.
Pré-requis serveur
La clé API doit rester sur le serveur PHP. Ne l’exposez jamais dans une page HTML, une application mobile ou un JavaScript public.
- Installer Guzzle si votre projet utilise Composer.
- Mettre la clé API dans
.env, jamais dans un template Blade ou un JavaScript public. - Envoyer
Authorization: Bearer ...oux-api-keysur chaque requête. - Générer une
Idempotency-Keypar mutation pour éviter les doubles envois. - Traiter
202 Acceptedcomme une acceptation en file, puis suivre la livraison via webhooks ou rapports DLR.
Guzzle
Exemple avec Guzzle.
bash
composer require guzzlehttp/guzzlephp
<?php
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$apiKey = getenv('COUSSEMA_API_KEY');
if (!$apiKey) {
throw new RuntimeException('COUSSEMA_API_KEY manquant.');
}
$client = new Client([
'base_uri' => getenv('COUSSEMA_BASE_URL') ?: 'https://api.coussema.com',
'timeout' => 15,
]);
try {
$response = $client->post('/v1/sms/send', [
'headers' => [
'Authorization' => 'Bearer ' . $apiKey,
'Content-Type' => 'application/json',
'Idempotency-Key' => bin2hex(random_bytes(16)),
'X-Request-Id' => 'php-send-' . date('YmdHis'),
],
'json' => [
'to' => '+243980395067',
'message' => 'Votre code : 4829',
'senderName' => 'MonApp',
'clientReference' => 'otp-login-4829',
],
]);
$payload = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
if (($payload['queued'] ?? false) !== true || ($payload['status'] ?? null) !== 'accepted') {
throw new RuntimeException('SMS non accepté par Coussema.');
}
echo 'SMS mis en file. Quota restant: ' . ($payload['remainingQuota'] ?? 'n/a');
} catch (RequestException $e) {
$body = $e->getResponse() ? (string) $e->getResponse()->getBody() : '';
throw new RuntimeException('Erreur API Coussema: ' . $body, 0, $e);
}Variables d’environnement
| Variable | Exemple | Description |
|---|---|---|
| COUSSEMA_API_KEY | csm_live_xxx | Clé API générée depuis le dashboard Coussema. Elle reste côté serveur. |
| COUSSEMA_BASE_URL | https://api.coussema.com | Hôte public de production. Les exemples appellent /v1/sms/send. |
| COUSSEMA_WEBHOOK_SECRET | csm_whsec_xxx | Secret reçu lors de la création d’un webhook, à stocker dans .env. |
Réponse API
POST /v1/sms/send répond avec un succès 2xx quand Coussema a validé, persisté et mis le SMS en file. La livraison finale arrive ensuite via webhook ou rapport DLR.
| Champ | Valeur |
|---|---|
| HTTP | 202 Accepted |
| queued | true |
| status | accepted |
| message.id | UUID Coussema durable |
| provider.messageId | toujours null |
cURL et Laravel
php
<?php
$payload = [
'to' => '+243980395067',
'message' => 'Votre code : 4829',
'senderName' => 'MonApp',
'clientReference' => 'otp-login-4829',
];
$ch = curl_init('https://api.coussema.com/v1/sms/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('COUSSEMA_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($body === false) {
throw new RuntimeException('Erreur réseau: ' . curl_error($ch));
}
curl_close($ch);
if ($status < 200 || $status >= 300) {
throw new RuntimeException('Erreur API Coussema HTTP ' . $status . ': ' . $body);
}
$result = json_decode($body, true, 512, JSON_THROW_ON_ERROR);php
<?php
namespace App\Http\Controllers;
use GuzzleHttp\Client;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class CoussemaSmsController
{
public function sendOtp(Request $request): JsonResponse
{
$request->validate([
'phone' => ['required', 'string'],
]);
$code = random_int(1000, 9999);
$client = new Client([
'base_uri' => config('services.coussema.base_url', 'https://api.coussema.com'),
'timeout' => 15,
]);
$response = $client->post('/v1/sms/send', [
'headers' => [
'Authorization' => 'Bearer ' . config('services.coussema.key'),
'Content-Type' => 'application/json',
'Idempotency-Key' => (string) Str::uuid(),
'X-Request-Id' => 'laravel-otp-' . Str::uuid(),
],
'json' => [
'to' => $request->input('phone'),
'message' => 'Votre code : ' . $code,
'senderName' => 'MonApp',
'clientReference' => 'otp-' . now()->timestamp,
],
]);
$payload = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
return response()->json([
'accepted' => ($payload['queued'] ?? false) === true,
'status' => $payload['status'] ?? null,
'request_id' => $response->getHeaderLine('x-request-id'),
], $response->getStatusCode());
}
}php
'coussema' => [
'key' => env('COUSSEMA_API_KEY'),
'base_url' => env('COUSSEMA_BASE_URL', 'https://api.coussema.com'),
'webhook_secret' => env('COUSSEMA_WEBHOOK_SECRET'),
],Vérifier un webhook en PHP
Les webhooks permettent de confirmer message.delivered et message.failed sans interroger l’API en boucle.
php
<?php
$rawBody = file_get_contents('php://input') ?: '';
$timestamp = $_SERVER['HTTP_X_COUSSEMA_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_COUSSEMA_SIGNATURE'] ?? '';
$secret = getenv('COUSSEMA_WEBHOOK_SECRET');
if (!$timestamp || !$signature || !$secret) {
http_response_code(400);
exit('Signature manquante.');
}
$signedPayload = $timestamp . '.' . $rawBody;
$expected = 'v1=' . hash_hmac('sha256', $signedPayload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Signature invalide.');
}
$event = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
// Exemple: message.delivered ou message.failed
// Mettez à jour votre commande, paiement, ticket ou session OTP ici.
http_response_code(204);Règles d’envoi
- Ne testez pas strictement HTTP 200: acceptez les succès 2xx conformes au contrat.
- Gardez votre propre
clientReferencepour retrouver l’OTP, commande ou paiement côté métier. - Sur timeout réseau, rejouez avec la même
Idempotency-Keysi vous ne connaissez pas le résultat.