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 ... ou x-api-key sur chaque requête.
  • Générer une Idempotency-Key par mutation pour éviter les doubles envois.
  • Traiter 202 Accepted comme une acceptation en file, puis suivre la livraison via webhooks ou rapports DLR.

Guzzle

Exemple avec Guzzle.

bash
composer require guzzlehttp/guzzle
php
<?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

VariableExempleDescription
COUSSEMA_API_KEYcsm_live_xxxClé API générée depuis le dashboard Coussema. Elle reste côté serveur.
COUSSEMA_BASE_URLhttps://api.coussema.comHôte public de production. Les exemples appellent /v1/sms/send.
COUSSEMA_WEBHOOK_SECRETcsm_whsec_xxxSecret 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.

ChampValeur
HTTP202 Accepted
queuedtrue
statusaccepted
message.idUUID Coussema durable
provider.messageIdtoujours 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 clientReference pour retrouver l’OTP, commande ou paiement côté métier.
  • Sur timeout réseau, rejouez avec la même Idempotency-Key si vous ne connaissez pas le résultat.