Retour à la documentationGuide PHP

Intégrer l’API Coussema en PHP

Ce guide est fait pour un backend PHP ou Laravel qui doit envoyer des OTP, notifications transactionnelles ou alertes métier via l’API REST Coussema.

Pré-requis côté 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.
Exemple PHP recommandé

GuzzleHttp\Client

Installation Composer
composer require guzzlehttp/guzzle
<?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);
}

COUSSEMA_API_KEY

Clé API générée depuis le dashboard Coussema. Elle reste côté serveur.

ck_live_xxx

COUSSEMA_BASE_URL

Hôte public de production. Les exemples appellent `/v1/sms/send`.

https://api.coussema.com

COUSSEMA_WEBHOOK_SECRET

Secret reçu lors de la création d’un webhook, à stocker dans `.env`.

csm_whsec_xxx

Comprendre la 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.

HTTP
202 Accepted
queued
true
status
accepted
message.id
UUID Coussema durable
provider.messageId
toujours null

PHP cURL natif

Variante sans dépendance Composer, utile pour un hébergement PHP simple.

<?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);

Laravel controller

Exemple de contrôleur pour déclencher un OTP depuis une route backend Laravel.

<?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());
    }
}

Configuration Laravel

Ajoutez cette entrée dans `config/services.php`, puis utilisez `config('services.coussema.key')`.

'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

$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 = 'sha256=' . 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);

Points à ne pas rater

Ces règles évitent les erreurs d’intégration les plus fréquentes chez les backends PHP.

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.