"> Aller au contenu principal
PHP PHP PHP v0.2.0 MIT

Withallo PHP SDK

qrcommunication/withallo-sdk

PHP 8.2+, guzzlehttp/guzzle ^7.8

Installation

bash
composer require qrcommunication/withallo-sdk
Prérequis : PHP 8.2+, ext-json. La dépendance guzzlehttp/guzzle ^7.8 est installée automatiquement par Composer.
Clé API requise. Générez votre clé API sur web.withallo.com/settings/api. Elle est envoyée telle quelle dans l'en-tête Authorization — PAS de préfixe Bearer.
Packagist qrcommunication/withallo-sdk
Version 0.2.0
Sources github.com/QrCommunication/sdk-withallo-php
Licence MIT
Base URL https://api.withallo.com/v1/api

Quick Start

Cinq ressources exposées sur un seul client : webhooks, appels, contacts, SMS et numéros. Chaque réponse est mappée vers un DTO fortement typé ; chaque valeur énumérée est un enum PHP 8.2 natif.

php
<?php

use QrCommunication\Withallo\AlloClient;
use QrCommunication\Withallo\AlloClientOptions;
use QrCommunication\Withallo\Dto\CreateContactInput;
use QrCommunication\Withallo\Dto\CreateWebhookInput;
use QrCommunication\Withallo\Dto\SendSmsInput;
use QrCommunication\Withallo\Dto\SendSmsFranceInput;
use QrCommunication\Withallo\Enum\WebhookTopic;
use QrCommunication\Withallo\Exception\AlloApiException;

$allo = new AlloClient(new AlloClientOptions(
    apiKey: $_ENV['ALLO_API_KEY'],
));

// ── Lister les numéros connectés ─────────────────────────────────

foreach ($allo->numbers->list() as $n) {
    echo "{$n->name} — {$n->number} ({$n->country})\n";
}

// ── Envoyer un SMS (US / international) ─────────────────────────

$sms = $allo->sms->send(new SendSmsInput(
    from: '+1234567890',
    to: '+0987654321',
    message: 'Hello from Allo',
));
echo $sms->content . ' @ ' . $sms->startDate->format(DATE_ATOM) . "\n";

// ── Envoyer un SMS depuis la France (sender_id pré-vérifié) ─────

$allo->sms->sendFrance(new SendSmsFranceInput(
    senderId: 'MyCompany',
    to: '+33612345678',
    message: 'Bonjour depuis Allo',
));

// ── Rechercher l'historique des appels ──────────────────────────

$calls = $allo->calls->search(
    alloNumber: '+1234567890',
    page: 0,
    size: 20,
);
foreach ($calls->results as $call) {
    echo "{$call->type->value} {$call->fromNumber} → {$call->toNumber} "
       . "({$call->lengthInMinutes} min)\n";
}

// ── Créer puis récupérer un contact ─────────────────────────────

try {
    $contact = $allo->contacts->create(new CreateContactInput(
        numbers: ['+33612345678'],
        name: 'Jean',
        lastName: 'Dupont',
        emails: ['jean.dupont@example.com'],
    ));

    $fetched = $allo->contacts->get($contact->id);
    echo "{$fetched->name} {$fetched->lastName}\n";
} catch (AlloApiException $e) {
    echo "Allo error [{$e->code}] : {$e->getMessage()}\n";
}

// ── Souscrire un webhook aux événements CALL_RECEIVED + SMS_RECEIVED ──

$webhook = $allo->webhooks->create(new CreateWebhookInput(
    alloNumber: '+1234567890',
    url: 'https://example.com/webhooks/allo',
    topics: [WebhookTopic::CALL_RECEIVED, WebhookTopic::SMS_RECEIVED],
    enabled: true,
));
echo "Webhook id : {$webhook->id}\n";

Configuration

AlloClientOptions est un value object readonly passé au constructeur de AlloClient. Seul apiKey est obligatoire — tous les autres champs ont une valeur par défaut raisonnable.

php
use QrCommunication\Withallo\AlloClientOptions;

$options = new AlloClientOptions(
    apiKey:  $_ENV['ALLO_API_KEY'],                // Obligatoire — clé brute, SANS préfixe Bearer
    baseUrl: 'https://api.withallo.com/v1/api',    // Défaut
    timeout: 30.0,                                 // Timeout Guzzle en secondes
    headers: ['X-App-Name' => 'my-application'],   // Headers HTTP supplémentaires
);
Paramètre Type Défaut Description
apiKey string Obligatoire. Clé API brute (pas de préfixe Bearer).
baseUrl string 'https://api.withallo.com/v1/api' URL de base de l'API Withallo
timeout float 30.0 Timeout Guzzle en secondes
headers array<string,string> [] Headers HTTP supplémentaires ajoutés à chaque requête
httpClient ?ClientInterface null Client PSR-18 personnalisé (tests, proxies). Par défaut, un client Guzzle construit à partir des options.
Scopes. Chaque endpoint exige un scope spécifique sur la clé API : WEBHOOKS_READ_WRITE, CONVERSATIONS_READ, CONTACTS_READ, CONTACTS_READ_WRITE, SMS_SEND. Un scope insuffisant retourne HTTP 403, levée en AlloApiException avec code API_KEY_INSUFFICIENT_SCOPE.

Ressources

$allo->webhooks

WEBHOOKS_READ_WRITE

Gérer les souscriptions webhook. Withallo pousse des événements (CALL_RECEIVED, SMS_RECEIVED, CONTACT_CREATED, CONTACT_UPDATED) vers l'URL HTTPS enregistrée.

Méthode HTTP Retourne
list(): Webhook[] GET /webhooks array<Webhook>
create(CreateWebhookInput $input): Webhook POST /webhooks Webhook
delete(string $webhookId): void DELETE /webhooks/{id} void
php
use QrCommunication\Withallo\Dto\CreateWebhookInput;
use QrCommunication\Withallo\Enum\WebhookTopic;

$webhook = $allo->webhooks->create(new CreateWebhookInput(
    alloNumber: '+1234567890',
    url:        'https://example.com/webhooks/allo',
    topics:     [WebhookTopic::CALL_RECEIVED, WebhookTopic::SMS_RECEIVED],
    enabled:    true,
));

foreach ($allo->webhooks->list() as $w) {
    echo "{$w->id} — {$w->url} — " . implode(',', array_map(fn($t) => $t->value, $w->topics)) . "\n";
}

$allo->webhooks->delete($webhook->id);
Casse des champs. L'endpoint REST retourne allo_number (snake_case) sur GET et alloNumber (camelCase) sur POST. Le SDK normalise les deux vers la propriété DTO Webhook::$alloNumber.

$allo->calls

CONVERSATIONS_READ
search(string $alloNumber, ?string $contactNumber = null, int $page = 0, int $size = 10): CallSearchResult

Recherche l'historique des appels filtré par numéro Allo et numéro contact optionnel. Pagination 0-indexée, plafond 100 par page.

ParamètreTypeDescription
$alloNumberstringObligatoire — votre numéro Allo (E.164).
$contactNumber?stringOptionnel — limiter à un contact.
$pageintPage 0-indexée. Défaut 0.
$sizeintTaille de page. Défaut 10, max 100.
php
$calls = $allo->calls->search(
    alloNumber: '+1234567890',
    page: 0,
    size: 20,
);

$pagination = $calls->metadata->pagination;
echo "Page {$pagination->currentPage}/{$pagination->totalPages}\n";

foreach ($calls->results as $call) {
    echo "{$call->type->value} {$call->fromNumber} → {$call->toNumber}\n";
    echo "  {$call->lengthInMinutes} min — {$call->startDate->format(DATE_ATOM)}\n";
    if ($call->recordingUrl) {
        echo "  Recording: {$call->recordingUrl}\n";
    }
    foreach ($call->transcript as $line) {
        echo "    [{$line->source->value}] {$line->text}\n";
    }
}

$allo->contacts

CONTACTS_READ / CONTACTS_READ_WRITE

CRUD sur les contacts et historique de conversation fusionné (appels + SMS).

Méthode HTTP Scope
search(int $page = 0, int $size = 10): ContactSearchResultGET /contactsCONTACTS_READ
get(string $contactId): ContactGET /contact/{id}CONTACTS_READ
create(CreateContactInput $input): ContactPOST /contactsCONTACTS_READ_WRITE
update(string $id, UpdateContactInput $input): ContactPUT /contacts/{id}CONTACTS_READ_WRITE
conversation(string $id, int $page = 0, int $size = 10): ConversationResultGET /contact/{id}/conversationCONVERSATIONS_READ
Subtilité de routage. Withallo utilise le pluriel /contacts pour la recherche et la création, mais le singulier /contact/{id} pour GET et conversation. Le SDK masque la différence.
php
use QrCommunication\Withallo\Dto\CreateContactInput;
use QrCommunication\Withallo\Dto\UpdateContactInput;
use QrCommunication\Withallo\Enum\ConversationEntryType;

$contact = $allo->contacts->create(new CreateContactInput(
    numbers:  ['+33612345678'],
    name:     'Jean',
    lastName: 'Dupont',
    jobTitle: 'CTO',
    emails:   ['jean.dupont@example.com'],
));

// emails / numbers REMPLACENT les tableaux existants lors de update
$updated = $allo->contacts->update($contact->id, new UpdateContactInput(
    jobTitle: 'CEO',
    emails: ['jean@example.com', 'contact@example.com'],
));

$conv = $allo->contacts->conversation($contact->id, page: 0, size: 20);
foreach ($conv->results as $entry) {
    if ($entry->type === ConversationEntryType::CALL) {
        echo "CALL {$entry->call->startDate->format(DATE_ATOM)} — {$entry->call->lengthInMinutes} min\n";
    } else {
        echo "SMS  {$entry->message->startDate->format(DATE_ATOM)} — {$entry->message->content}\n";
    }
}

$allo->sms

SMS_SEND

Envoi de SMS sortants. Deux variantes existent car le payload dépend du pays de destination.

Méthode Entrée Cas d'usage
send(SendSmsInput $input): SentSms SendSmsInput(from, to, message) US / international. from doit être un de vos numéros Allo.
sendFrance(SendSmsFranceInput $input): SentSms SendSmsFranceInput(senderId, to, message) France. Exige un sender alphanumérique pré-vérifié (3–11 caractères) — décision ARCEP (janvier 2023) bloquant les SMS professionnels depuis un numéro mobile standard.
php
use QrCommunication\Withallo\Dto\SendSmsInput;
use QrCommunication\Withallo\Dto\SendSmsFranceInput;
use QrCommunication\Withallo\Exception\AlloApiException;

$sms = $allo->sms->send(new SendSmsInput(
    from:    '+1234567890',
    to:      '+0987654321',
    message: 'Hello from Allo',
));
echo "Sent : {$sms->content} @ {$sms->startDate->format(DATE_ATOM)}\n";

// France — sender_id doit être pré-validé par le support Allo
try {
    $allo->sms->sendFrance(new SendSmsFranceInput(
        senderId: 'MyCompany',
        to:       '+33612345678',
        message:  'Bonjour depuis Allo',
    ));
} catch (AlloApiException $e) {
    if ($e->code === 'INVALID_SENDER_ID') {
        echo "Sender non vérifié — contacter le support Allo.\n";
    }
    throw $e;
}

$allo->numbers

CONVERSATIONS_READ
list(): array<PhoneNumber>

Liste les numéros Allo connectés à votre compte.

php
foreach ($allo->numbers->list() as $n) {
    $shared = $n->isSharedNumber ? ' (shared)' : '';
    echo "{$n->country}  {$n->name}{$shared} — {$n->number}\n";
}

DTOs & Enums

Chaque réponse est désérialisée en DTO readonly. Chaque énumération est un enum PHP 8.2 natif (backed) — autocomplétion et analyse statique assurées.

DTOs principaux

DTO Champs clés
Webhookid, alloNumber, enabled, url, topics: WebhookTopic[]
Callid, fromNumber, toNumber, lengthInMinutes, type: CallDirection, result: ?CallResult, startDate: DateTimeImmutable, transcript: TranscriptLine[]
Contactid, name, lastName, jobTitle, numbers: string[], emails: string[], company: ?Company, createdAt: DateTimeImmutable
SentSmsfromNumber, senderId, toNumber, type: CallDirection, content, startDate
PhoneNumbernumber, name, country, isSharedNumber
ConversationEntrytype: ConversationEntryType, call: ?Call, message: ?SmsMessage
PaginationMetadatapagination->totalPages, pagination->currentPage

DTOs d'entrée

DTOConstructeur
CreateWebhookInputalloNumber, url, topics: WebhookTopic[], enabled = true
CreateContactInputnumbers: string[], name?, lastName?, jobTitle?, website?, emails?
UpdateContactInputname?, lastName?, jobTitle?, website?, emails?, numbers?
SendSmsInputfrom, to, message
SendSmsFranceInputsenderId, to, message

Enums (PHP 8.2 natif)

php
namespace QrCommunication\Withallo\Enum;

enum WebhookTopic: string {
    case CALL_RECEIVED    = 'CALL_RECEIVED';
    case SMS_RECEIVED     = 'SMS_RECEIVED';
    case CONTACT_CREATED  = 'CONTACT_CREATED';
    case CONTACT_UPDATED  = 'CONTACT_UPDATED';
}

enum CallDirection: string {
    case INBOUND  = 'INBOUND';
    case OUTBOUND = 'OUTBOUND';
}

enum CallResult: string {
    case ANSWERED             = 'ANSWERED';
    case VOICEMAIL            = 'VOICEMAIL';
    case TRANSFERRED_AI       = 'TRANSFERRED_AI';
    case TRANSFERRED_EXTERNAL = 'TRANSFERRED_EXTERNAL';
    case BLOCKED              = 'BLOCKED';
    case FAILED               = 'FAILED';
}

enum TranscriptSource: string {
    case AGENT    = 'AGENT';
    case EXTERNAL = 'EXTERNAL';
    case USER     = 'USER';
}

enum ConversationEntryType: string {
    case CALL         = 'CALL';
    case TEXT_MESSAGE = 'TEXT_MESSAGE';
}

WebhookReceiver

Le SDK embarque un WebhookReceiver indépendant du framework qui parse le JSON entrant, dispatche sur des handlers typés et valide la valeur allo_number .

Pas de signature HMAC. À date (avril 2026), Withallo ne publie pas de secret de signature pour les webhooks entrants. Durcissez votre endpoint avec HTTPS, une URL non devinable et un allo_number allow-list.
php
use QrCommunication\Withallo\Webhook\WebhookReceiver;
use QrCommunication\Withallo\Webhook\Payload\CallReceivedPayload;
use QrCommunication\Withallo\Webhook\Payload\SmsReceivedPayload;
use QrCommunication\Withallo\Webhook\Payload\ContactEventPayload;

$receiver = (new WebhookReceiver())
    ->allowedAlloNumbers(['+1234567890'])
    ->onCallReceived(function (CallReceivedPayload $p): void {
        // Persister, notifier, enqueuer l'analyse du transcript…
    })
    ->onSmsReceived(function (SmsReceivedPayload $p): void {
        // …
    })
    ->onContactCreated(function (ContactEventPayload $p): void {
        // …
    })
    ->onContactUpdated(function (ContactEventPayload $p): void {
        // …
    });

// Dans votre contrôleur (exemple Laravel) :
Route::post('/webhooks/allo/{token}', function (Request $request, string $token) use ($receiver) {
    if ($token !== config('services.allo.webhook_token')) {
        abort(404);
    }
    $receiver->handle($request->getContent());
    return response()->noContent();
});

Gestion des erreurs

Chaque appel du SDK lance AlloApiException sur 4xx/5xx. L'exception expose code (machine), status (HTTP) et details (erreurs de validation).

php
use QrCommunication\Withallo\Exception\AlloApiException;

try {
    $allo->sms->sendFrance(new SendSmsFranceInput(
        senderId: 'Unverified',
        to: '+33612345678',
        message: 'Hi',
    ));
} catch (AlloApiException $e) {
    echo "HTTP {$e->status} — code={$e->code} — {$e->getMessage()}\n";

    foreach ($e->details as $detail) {
        echo "  · {$detail->field} : {$detail->message}\n";
    }

    match ($e->code) {
        'API_KEY_INVALID'            => /* 401 */ null,
        'API_KEY_INSUFFICIENT_SCOPE' => /* 403 */ null,
        'RATE_LIMITED'               => /* 429 — respect Retry-After */ null,
        'INVALID_SENDER_ID'          => /* 400 France */ null,
        default                      => throw $e,
    };
}

Codes d'erreur courants

HTTP code Signification
401API_KEY_INVALIDClé API absente ou invalide
403API_KEY_INSUFFICIENT_SCOPELa clé n'a pas le scope requis
404WEBHOOK_NOT_FOUND, CONTACT_NOT_FOUNDRessource inexistante
400INVALID_SENDER_IDsender_id France non vérifié
429RATE_LIMITEDAttendre et réessayer après l'en-tête Retry-After

Exemples

Parcourir tous les contacts

php
/** @var list<Contact> $all */
$all = [];
$page = 0;
$size = 100;

do {
    $batch = $allo->contacts->search(page: $page, size: $size);
    $all = array_merge($all, $batch->results);
    $page++;
} while ($page < $batch->metadata->pagination->totalPages);

echo count($all) . " contacts\n";

Retry avec back-off exponentiel sur rate limiting

php
use QrCommunication\Withallo\Exception\AlloApiException;

function withRetry(callable $fn, int $maxAttempts = 5): mixed
{
    $attempt = 0;
    while (true) {
        try {
            return $fn();
        } catch (AlloApiException $e) {
            if ($e->code !== 'RATE_LIMITED' || $attempt >= $maxAttempts) {
                throw $e;
            }
            $waitMs = min(1000 * (2 ** $attempt), 30_000);
            usleep($waitMs * 1000);
            $attempt++;
        }
    }
}

$sms = withRetry(fn () => $allo->sms->send(new SendSmsInput(
    from: '+1234567890', to: '+0987654321', message: 'Hi',
)));

Intégration Laravel (service provider)

Enregistrer le client en singleton afin que la connexion Guzzle et la configuration de la clé API soient partagées dans l'application.

php
// config/services.php
'allo' => [
    'api_key'        => env('ALLO_API_KEY'),
    'webhook_token'  => env('ALLO_WEBHOOK_TOKEN'),
],

// app/Providers/AppServiceProvider.php
use QrCommunication\Withallo\AlloClient;
use QrCommunication\Withallo\AlloClientOptions;

public function register(): void
{
    $this->app->singleton(AlloClient::class, function () {
        return new AlloClient(new AlloClientOptions(
            apiKey:  config('services.allo.api_key'),
            timeout: 30.0,
        ));
    });
}

// Utilisation dans n'importe quel contrôleur / service :
public function send(AlloClient $allo, SendSmsRequest $request)
{
    $sms = $allo->sms->send(new SendSmsInput(
        from:    $request->validated('from'),
        to:      $request->validated('to'),
        message: $request->validated('message'),
    ));

    return response()->json(['sent_at' => $sms->startDate->format(DATE_ATOM)]);
}

Skill AI

Le SDK inclut un skill AI qui installe une documentation complète directement dans votre agent de code (Claude Code, Cursor, Codex, Windsurf, Cline, Aider, Gemini CLI).

bash
bash vendor/qrcommunication/withallo-sdk/skill/install.sh

Installation manuelle : cp -r vendor/qrcommunication/withallo-sdk/skill ~/.claude/skills/sdk-withallo