@qrcommunication/withallo-sdk
v0.2.10
Published
TypeScript SDK for Withallo (Allo) API — webhooks, calls, contacts, SMS and phone numbers. Isomorphic core + optional React hooks.
Maintainers
Readme
Withallo SDK — JavaScript / TypeScript
SDK TypeScript isomorphe pour l'API Withallo (Allo). Core utilisable partout (Node 18+, navigateurs modernes, Deno, React Native, edge runtimes) + un sous-module /react avec des hooks prêts à l'emploi.
Couvre les 5 domaines de l'API publique : Webhooks, Calls, Contacts, SMS, Phone Numbers, plus un WebhookReceiver pour traiter les événements entrants (CALL_RECEIVED, SMS_RECEIVED, CONTACT_CREATED, CONTACT_UPDATED).
Installation
# npm
npm install @qrcommunication/withallo-sdk
# pnpm
pnpm add @qrcommunication/withallo-sdk
# yarn
yarn add @qrcommunication/withallo-sdk
# bun
bun add @qrcommunication/withallo-sdkPrérequis / Requirements : fetch global (Node 18+, navigateurs modernes, Deno, Bun). Passez un polyfill via options.fetch si votre runtime n'en a pas.
Générez votre clé API depuis web.withallo.com/settings/api.
Table des matières / Table of contents
- Démarrage rapide / Quick start
- Utilisation avec React
- Référence des ressources / Resources reference
- WebhookReceiver — réception des événements
- Gestion d'erreurs / Error handling
- Sécurité des webhooks / Webhook security
- Développement / Development
Démarrage rapide / Quick start
Core TypeScript (Node, browser, Deno, React Native)
import { WithalloClient, WebhookTopic } from "@qrcommunication/withallo-sdk";
const client = new WithalloClient({
apiKey: process.env.WITHALLO_API_KEY!,
});
// Créer un webhook / Create a webhook
await client.webhooks.create({
alloNumber: "+1234567890",
url: "https://example.com/webhooks/allo",
topics: [WebhookTopic.CALL_RECEIVED, WebhookTopic.SMS_RECEIVED],
});
// Envoyer un SMS (US) / Send SMS (US)
await client.sms.send({
from: "+1234567890",
to: "+0987654321",
message: "Hello from Withallo SDK",
});
// Envoyer un SMS France (Sender ID vérifié requis)
await client.sms.sendFrance({
senderId: "MyCompany",
to: "+33612345678",
message: "Bonjour depuis le SDK Withallo",
});
// Rechercher des appels / Search calls
const result = await client.calls.search({
alloNumber: "+1234567890",
size: 50,
});
// Tester la connexion / Test connectivity
await client.testConnection(); // Promise<boolean>Utilisation avec React / React usage
Le package expose un point d'entrée séparé @qrcommunication/withallo-sdk/react avec un provider et des hooks.
import { WithalloProvider, useSendSms, useWebhooks } from "@qrcommunication/withallo-sdk/react";
// 1. Envelopper votre app
function App() {
return (
<WithalloProvider options={{ apiKey: process.env.NEXT_PUBLIC_WITHALLO_KEY! }}>
<Dashboard />
</WithalloProvider>
);
}
// 2. Utiliser les hooks
function SendSmsForm() {
const { run, isPending, error, isSuccess } = useSendSms();
return (
<form
onSubmit={(e) => {
e.preventDefault();
const form = new FormData(e.currentTarget);
void run({
from: form.get("from") as string,
to: form.get("to") as string,
message: form.get("message") as string,
});
}}
>
<input name="from" placeholder="+1..." />
<input name="to" placeholder="+1..." />
<textarea name="message" />
<button disabled={isPending}>
{isPending ? "Sending..." : "Send SMS"}
</button>
{error && <p>Error: {error.message}</p>}
{isSuccess && <p>Sent ✓</p>}
</form>
);
}
function WebhooksList() {
const { webhooks, isLoading, error, refresh } = useWebhooks();
if (isLoading) return <p>Loading…</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{webhooks.map((w) => (
<li key={w.id ?? w.url}>
{w.url} → {w.topics.join(", ")}
</li>
))}
<button onClick={() => void refresh()}>Refresh</button>
</ul>
);
}Sécurité : n'exposez jamais votre API key via
NEXT_PUBLIC_*en production. Pour des apps publiques, passez par votre propre backend en proxy. Le hook sert principalement aux dashboards admin côté serveur ou à des intégrations Node.
Hooks disponibles
| Hook | Description |
|------|-------------|
| useWithallo() | Retourne le WithalloClient courant (lève si pas de provider) |
| useSendSms() | Mutation client.sms.send() avec isPending / data / error |
| useSendSmsFrance() | Mutation client.sms.sendFrance() |
| useWebhooks() | Query auto-fetch de la liste des webhooks + refresh() |
| useCreateWebhook() | Mutation client.webhooks.create() |
| useDeleteWebhook() | Mutation client.webhooks.delete() |
| useAsyncAction(fn) | Hook bas-niveau pour wrapper n'importe quelle action async |
Tous les hooks useXxxAction retournent { data, error, isPending, isSuccess, isError, run, reset }.
Référence des ressources / Resources reference
Architecture
WithalloClient
├─ webhooks → WebhooksResource (scope: WEBHOOKS_READ_WRITE)
├─ calls → CallsResource (scope: CONVERSATIONS_READ)
├─ contacts → ContactsResource (scopes: CONTACTS_READ / CONTACTS_READ_WRITE)
├─ sms → SmsResource (scope: SMS_SEND)
├─ phoneNumbers → PhoneNumbersResource (scope: CONVERSATIONS_READ)
└─ webhookReceiver() → WebhookReceiver (no network — parses incoming payloads)Webhooks
await client.webhooks.list();
// => Webhook[]
await client.webhooks.create({
alloNumber: "+1234567890",
url: "https://example.com/hook",
topics: [WebhookTopic.CALL_RECEIVED],
enabled: true,
});
// => Webhook
await client.webhooks.delete("web-2NfDKEm9sF8xK3pQr1Zt");Calls
const page = await client.calls.search({
alloNumber: "+1234567890", // requis
contactNumber: "+0987654321", // optionnel
page: 0, // défaut 0 (0-indexed)
size: 10, // défaut 10, max 100
});
// => { results: Call[]; metadata: { pagination?: { total_pages, current_page } } }Contacts
await client.contacts.get("cnt_abc123");
await client.contacts.search(0, 20);
await client.contacts.searchConversation("cnt_abc123", 0, 20);
await client.contacts.create({
numbers: ["+15551234567"],
name: "John",
lastName: "Doe",
emails: ["[email protected]"],
});
await client.contacts.update("cnt_abc123", {
last_name: "Smith",
job_title: "CTO",
emails: ["[email protected]", "[email protected]"],
});SMS
// US / International — envoi depuis un numéro Allo
await client.sms.send({
from: "+1234567890",
to: "+0987654321",
message: "Hello",
});
// France — Sender ID vérifié requis (alphanumeric 3–11 chars OR short code)
await client.sms.sendFrance({
senderId: "MyCompany",
to: "+33612345678",
message: "Bonjour",
});Phone Numbers
const numbers = await client.phoneNumbers.list();
// => PhoneNumber[]WebhookReceiver — réception des événements
Parse les payloads entrants et dispatch vers les handlers.
import { WebhookReceiver, WebhookTopic } from "@qrcommunication/withallo-sdk";
const receiver = new WebhookReceiver();
receiver
.on(WebhookTopic.CALL_RECEIVED, async (event) => {
const id = event.get<string>("id");
const summary = event.get<string>("one_sentence_summary");
// ... votre logique
})
.on(WebhookTopic.SMS_RECEIVED, async (event) => {
const content = event.get<string>("content");
const direction = event.get<"INBOUND" | "OUTBOUND">("direction");
});
// Exemple: Next.js App Router Route Handler
export async function POST(request: Request): Promise<Response> {
const rawBody = await request.text();
try {
await receiver.handle(rawBody);
return new Response(null, { status: 200 });
} catch {
return new Response(null, { status: 400 });
}
}
// Exemple: Express
app.post("/webhooks/allo", async (req, res) => {
const rawBody = JSON.stringify(req.body);
await receiver.handle(rawBody);
res.status(200).end();
});WebhookEvent expose :
.topic—WebhookTopic.data— payload typé (TDatagénérique).raw— enveloppe JSON complète.isCall() / isSms() / isContactCreated() / isContactUpdated().get<T>(path, fallback?)— accès dot-notation
Gestion d'erreurs / Error handling
WithalloError
├── ApiError (httpStatus, responseBody, getErrorCode(), getDetails())
│ ├── AuthenticationError → 401 (API_KEY_INVALID)
│ ├── ForbiddenError → 403 (requiredScopes(): string[])
│ ├── ValidationError → 400/422 (errors(): Record<string, string>)
│ ├── NotFoundError → 404
│ └── RateLimitError → 429 (retryAfterSeconds: number | null)
└── InvalidWebhookPayloadError → payload webhook malformé (parse)import { ForbiddenError, RateLimitError } from "@qrcommunication/withallo-sdk";
try {
await client.webhooks.create({ /* ... */ });
} catch (err) {
if (err instanceof ForbiddenError) {
console.error("Scope manquant :", err.requiredScopes());
} else if (err instanceof RateLimitError) {
await new Promise((r) => setTimeout(r, (err.retryAfterSeconds ?? 1) * 1000));
// retry...
} else {
throw err;
}
}Sécurité des webhooks / Webhook security
Important : au moment de la publication de ce SDK (avril 2026), la documentation publique de Withallo ne spécifie aucun en-tête de signature HMAC permettant de vérifier cryptographiquement l'origine des webhooks. Ce SDK valide la forme du payload mais ne peut pas authentifier l'expéditeur.
Hardening recommandé :
- Servez votre endpoint webhook en HTTPS uniquement.
- Utilisez une URL secrète et non devinable (token dans le path).
- Whitelist des IPs egress Withallo au firewall si publiées.
- Rejetez les payloads dont
allo_numberne fait pas partie de vos propres numéros. - Répondez
200 OKen moins de 30 secondes.
Si Withallo publie un schéma de signature, une méthode WebhookReceiver.verifySignature(rawBody, signature, secret) sera ajoutée sans breaking change.
Documentation & exemples / Documentation & examples
| Fichier | Contenu |
|---|---|
| docs/openapi.yaml | Spécification OpenAPI 3.1 complète de l'API Withallo (endpoints, schémas de payloads, erreurs, sécurité) |
| docs/ARCHITECTURE.md | Schémas mermaid : layers, request lifecycle, pipeline webhook, classes d'erreurs, extension points |
| docs/examples/nextjs-webhook-route.ts | Route Handler Next.js App Router pour réception webhook |
| docs/examples/express-server.ts | Serveur Express : endpoint SMS + endpoint webhook |
| docs/examples/react-dashboard.tsx | Dashboard React complet avec les hooks du SDK |
| docs/examples/error-handling.ts | Gestion d'erreurs exhaustive (rate-limit retry, scope, validation…) |
| docs/examples/live-smoke-test.ts | Test end-to-end contre l'API réelle |
| llms.txt | Index au format llmstxt.org pour les LLMs / agents IA |
| CLAUDE.md / AGENTS.md | Instructions pour outils IA (Claude, Cursor, Copilot, Codex) |
| skill/SKILL.md | Descripteur de skill Claude Code |
Développement / Development
pnpm install
pnpm build # ESM + CJS + .d.ts via tsup
pnpm test # vitest run
pnpm test:watch # vitest --watch
pnpm typecheck # tsc --noEmit
pnpm lint # eslintRelease process
Ce package suit le Semantic Versioning. Les versions sont automatisées par release-please :
- Merger une PR sur
mainavec des commits au format Conventional Commits (feat:,fix:, etc.) - release-please crée une PR "chore(release): X.Y.Z" avec le changelog auto-généré
- Merger cette PR tag la release et publie sur npm via le workflow
release.yml
Licence / License
MIT © 2026 QrCommunication
