Iwana
← Développeurs

API REST v1

Connectez votre CRM ou ERP à Iwana en quelques heures. Clé API pour un espace de travail, OAuth 2.0 pour un utilisateur qui vous autorise, toute l’API dès la première clé et des webhooks signés.

REST JSON Clés API OAuth 2.0 Webhooks HMAC Accès complet

Démarrage rapide

Trois étapes pour votre première requête réussie. Tout se configure depuis le tableau de bord pro.

  1. Créez une clé APIParamètres → Développeurs → Nouvelle clé. Copiez le secret immédiatement — il n’est affiché qu’une fois. Vous publiez un produit que plusieurs entreprises installeront ? Passez par OAuth, ci-dessous.
  2. Testez l’authentificationAppelez GET /integrations/me pour vérifier l’organisation et les permissions actives.
  3. Synchronisez vos donnéesLisez le catalogue et l’équipe, créez ou mettez à jour clients et rendez-vous, puis abonnez un webhook pour les mises à jour temps réel.

Exemple curl — remplacez la clé par la vôtre :

curl -s https://www.iwana.site/api/v1/pro/integrations/me \
  -H "Authorization: Bearer iw_live_xxxxxxxx_your_secret_here"

Authentification

Deux façons d’atteindre un espace de travail, qui arrivent sur la même API, les mêmes routes /pro/* et les mêmes contrôles de permission. Une clé API nomme l’espace de travail ; un jeton OAuth nomme l’utilisateur, l’espace de travail et votre application. Ni l’un ni l’autre ne gère les identifiants (clés API, webhooks, plugins) ni n’invoque l’IA : ces actions restent dans l’application.

Clé APIJeton OAuth
Qui l’émetUn OWNER/ADMIN de l’espace, dans Paramètres → DéveloppeursL’utilisateur, sur la page de consentement, pour un espace PRO
Ce qu’il désigneL’espace de travailL’utilisateur et l’espace et l’application
PermissionsToute l’API décrite iciToute l’API, dans la limite du rôle de l’utilisateur, revérifiée à chaque requête
Durée de vieJusqu’à révocationAccès 1 h · refresh 3 ans · jusqu’à révocation
Révoqué parAdmin de l’espace · équipe IwanaAdmin de l’espace (« Applications connectées ») · équipe Iwana (désactivation de l’app)
En-têteAuthorization: Bearer iw_live_…Authorization: Bearer iwa_at_…
Authorization: Bearer iw_live_<prefix>_<secret>

Clés production

Préfixe iw_live_* — accès aux données réelles de l’organisation.

Clés sandbox

Préfixe iw_test_* — environnements de test et intégration continue.

L’organisation est embarquée dans la clé ou le jeton : vous n’avez pas besoin d’envoyer x-workspace-id ni d’autres en-têtes de contexte.

OAuth 2.0 — Sign in with IWANA

Authorization Code avec secret client. PKCE (S256) est honoré quand le client l’envoie, sans être obligatoire. Les jetons sont opaques : pas d’OpenID Connect, pas de JWKS, pas d’id_token — l’identité vient de GET /oauth/userinfo. Le jeton ouvre toute l’API décrite ici, dans la limite du rôle de l’utilisateur : le jeton d’un VIEWER ne peut pas écrire.

EndpointMéthodeAuthentification
https://www.iwana.site/oauth/authorizenavigateurla session Iwana de l’utilisateur
https://www.iwana.site/api/v1/oauth/tokenPOST, form-encodedclient_id + client_secret (corps ou HTTP Basic)
https://www.iwana.site/api/v1/oauth/userinfoGETBearer iwa_at_…

1. Enregistrer votre application

L’équipe Iwana crée l’application : nom, logo, jusqu’à cinq redirect_uri comparées à l’identique (http ou https, sans fragment). Vous recevez un client_id (iwa_…) et un client_secret (iwa_cs_…) — le secret n’est affiché qu’une fois et stocké haché. Toutes les applications sont confidentielles : le secret est requis au token endpoint, PKCE s’y ajoute mais ne le remplace pas.

Écrivez à [email protected] avec le nom de l’application, un logo et vos URL de retour.

2. Envoyer l’utilisateur vers Iwana

GET https://www.iwana.site/oauth/authorize
    ?client_id=iwa_…
    &redirect_uri=https://crm.example/auth/iwana/callback
    &response_type=code
    &state=<random>
    &code_challenge=<S256>&code_challenge_method=S256

Pas de paramètre scope : le jeton couvre toute l’API. Un client_id inconnu ou une redirect_uri non enregistrée affiche une page sans issue et ne redirige jamais. Un utilisateur sans espace PRO voit « réservé aux professionnels » ; avec plusieurs espaces, il choisit lequel autoriser. Un consentement déjà accordé saute la carte.

Le navigateur revient sur redirect_uri?code=iwa_ac_…&state=…, ou ?error=access_denied&state=… si l’utilisateur a refusé. Le code vit 60 secondes et ne sert qu’une fois.

3. Échanger le code

POST https://www.iwana.site/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=iwa_ac_…&redirect_uri=<same as step 2>
&client_id=iwa_…&client_secret=iwa_cs_…&code_verifier=…
{
  "access_token": "iwa_at_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "iwa_rt_…",
  "scope": "…"
}

Envoyez code_verifier seulement si vous avez envoyé un code_challenge à l’étape 2. Le client peut s’authentifier dans le corps ou en HTTP Basic.

4. Appeler l’API

GET https://www.iwana.site/api/v1/pro/clients
Authorization: Bearer iwa_at_…

Pas d’en-tête x-workspace-id : le jeton porte l’espace de travail. Les enregistrements écrits ainsi sont attribués à votre application dans le journal d’audit et portent source: API.

5. Rafraîchir

grant_type=refresh_token&refresh_token=iwa_rt_…&client_id=iwa_…&client_secret=iwa_cs_…

Renvoie un nouvel access_token ; le refresh_token reste le même. Après trois ans, ou une fois le grant révoqué, la réponse est invalid_grant et l’utilisateur doit se reconnecter. Rafraîchissez à l’heure, pas à chaque requête : le token endpoint a sa propre limite, bien plus basse.

Qui est connecté

GET https://www.iwana.site/api/v1/oauth/userinfo
Authorization: Bearer iwa_at_…

{
  "sub": "<user id>", "email": "…", "email_verified": true, "name": "…", "picture": null,
  "org_id": "<workspace id>", "org_name": "Plomberie Dupont", "role": "OWNER",
  "scope": "…"
}

sub + org_id est la paire à lier à votre propre fiche utilisateur.

Erreurs OAuth

Le token endpoint parle RFC 6749 : { "error": "invalid_grant", "error_description": "…" } avec invalid_request, invalid_client (401, avec WWW-Authenticate), invalid_grant, unsupported_grant_type. Un bearer refusé sur une route PRO est un 401 avec l’enveloppe habituelle { "error": { "code": "invalid_token" } }.

Durées de vie

QuoiVitPuis
Code d’autorisation60 s, un seul usageinvalid_grant
Access token1 heurerefresh
Refresh token3 ans, fixel’utilisateur se reconnecte
Grantjusqu’à révocationtous les jetons meurent d’un coup
Clé APIjusqu’à révocation401

Révocation

Il n’y a pas d’endpoint de révocation : l’admin de l’espace coupe votre application depuis Paramètres → Développeurs → Applications connectées, et l’équipe Iwana peut désactiver l’application partout. Dans les deux cas, chaque jeton est refusé dès la requête suivante, y compris un access token fraîchement rafraîchi. Un membre retiré ou suspendu perd ses jetons de la même manière.

Avec une librairie générique

Tout client OAuth 2.0 standard fonctionne. Deux configurations prêtes à coller :

Passport (passport-oauth2)

new OAuth2Strategy({
  authorizationURL: 'https://www.iwana.site/oauth/authorize',
  tokenURL: 'https://www.iwana.site/api/v1/oauth/token',
  clientID, clientSecret, callbackURL,
  pkce: true, state: true,
}, async (accessToken, refreshToken, _profile, done) => {
  const me = await fetch('https://www.iwana.site/api/v1/oauth/userinfo', {
    headers: { authorization: `Bearer ${accessToken}` },
  }).then((r) => r.json());
  done(null, { id: me.sub, orgId: me.org_id, accessToken, refreshToken });
});

NextAuth / Auth.js

{
  id: 'iwana', name: 'IWANA', type: 'oauth',
  authorization: 'https://www.iwana.site/oauth/authorize',
  token: 'https://www.iwana.site/api/v1/oauth/token',
  userinfo: 'https://www.iwana.site/api/v1/oauth/userinfo',
  clientId, clientSecret, checks: ['pkce', 'state'],
  profile: (p) => ({ id: p.sub, email: p.email, name: p.name, image: p.picture }),
}

URL de base

Tous les endpoints vivent sous le préfixe PRO :

https://www.iwana.site/api/v1/pro

Les exemples ci-dessous omettent ce préfixe pour la lisibilité. Les identifiants (`id`, `clientId`, `orgId`…) sont des chaînes opaques sans préfixe : stockez-les tels quels, ne les analysez pas.

Vérifier votre clé

GET/integrations/me

Retourne orgId, acteur, permissions, locale et fuseau horaire. Idéal comme health-check d’intégration.

{
  "orgId": "<workspace id>",
  "actor": { "kind": "api_key", "id": "<key id>" },
  "permissions": ["appointment:read", "appointment:write", …],
  "locale": "fr",
  "timezone": "Europe/Paris"
}

Rendez-vous

CRUD sur les rendez-vous de type APPOINTMENT uniquement (pas les blocs d’indisponibilité). La source est automatiquement marquée API lors d’un appel par clé ou par jeton.

GET/appointments

Liste paginée. Filtres : from, to (ISO date), status (PENDING, CONFIRMED, CANCELLED…).

GET/appointments/{id}

Détail d’un rendez-vous avec client, prestation et membres assignés.

POST/appointments

Crée un rendez-vous. serviceId et clientId sont requis ; startAt / endAt en ISO 8601 UTC.

PATCH/appointments/{id}/status

Change le statut (ex. CONFIRMED, CANCELLED) sans réécrire tout l’objet.

DELETE/appointments/{id}

Annule le rendez-vous (soft cancel côté métier).

Corps de création

{
  "serviceId": "<service id>",
  "clientId": "<client id>",
  "startAt": "2026-08-10T09:00:00.000Z",
  "endAt": "2026-08-10T10:00:00.000Z",
  "memberIds": ["<member id>"],
  "customFields": { "crmDealId": "42" },
  "internalNotes": "Synced from CRM"
}

Clients

Gérez la fiche client pour alimenter vos rendez-vous et garder le CRM aligné.

GET/clients

Liste des clients de l’organisation avec recherche et pagination.

GET/clients/{id}

Fiche complète : coordonnées, tags, champs personnalisés.

POST/clients

Crée ou met à jour (upsert selon email/téléphone selon vos règles métier).

DELETE/clients/{id}

Supprime le client (selon les règles de rétention de l’organisation).

Exemple de création

{
  "firstName": "Marie",
  "lastName": "Dupont",
  "email": "[email protected]",
  "phone": "+33612345678",
  "customFields": { "hubspotId": "12345" }
}

Sites & équipements

Un site est une adresse où le travail a lieu : un contact en a généralement plusieurs, et c’est le site — pas le contact — qui porte le code de portail, les coordonnées GPS et les personnes à joindre sur place. Un équipement est ce qui y est installé.

GET/sites

Adresses d’un espace de travail. Filtrez par ?clientId= pour celles d’un contact.

GET/sites/{id}

Un site avec ses contacts (propriétaire, agence, locataire…).

POST/sites

Créer un site. clientId et line1 sont requis ; lat/lng si vous les avez déjà.

GET/equipments

Équipements installés. Filtrez par ?siteId= ou ?clientId=.

Un site appartient à un contact et un équipement à un site : créez le contact avant le site, et filtrez les équipements par ?siteId= pour retrouver ceux d’une adresse.

Catalogue & équipe

Données de référence en lecture seule — utiles pour mapper vos IDs avant de créer des rendez-vous.

GET/catalog/services

Prestations proposées : durée, prix, catégorie.

GET/catalog/locations

Lieux d’exercice (salles, adresses).

GET/catalog/categories

Arborescence des catégories de prestations.

GET/team

Membres assignables aux rendez-vous.

GET/availability

Créneaux et fermetures exceptionnelles.

GET/business-rules

Snapshot des règles métier (délais, annulation, etc.).

Mapping IDs externes

Pour une synchronisation CRM idempotente, stockez la correspondance entre vos IDs et ceux d’Iwana. Évite les doublons quand le même contact est reçu plusieurs fois.

GET/external-refs?provider=&entityType=&externalId=

Résout un ID externe vers l’ID Iwana local (client, rendez-vous…).

PUT/external-refs

Crée ou met à jour un mapping bidirectionnel.

PUT /external-refs
{
  "provider": "hubspot",
  "entityType": "client",
  "localId": "<client id>",
  "externalId": "12345"
}

Webhooks sortants

Abonnez une URL HTTPS depuis Paramètres → Développeurs. Iwana envoie un POST JSON à chaque événement métier, signé pour que vous puissiez vérifier l’authenticité.

Événements disponibles

appointment.createdappointment.updatedappointment.cancelledclient.createdclient.updated

En-têtes de livraison

Iwana-Signature: sha256=<hex hmac-sha256 of the raw body>
Iwana-Event: appointment.created
Iwana-Delivery: <uuid, unique per attempt>

Corps de l’événement

{
  "event": "appointment.created",
  "orgId": "<workspace id>",
  "data": {
    "id": "<appointment id>",
    "startAt": "2026-08-10T09:00:00.000Z",
    "status": "PENDING"
  },
  "occurredAt": "2026-08-07T12:00:00.000Z"
}

Vérification HMAC — calculez SHA-256 HMAC du corps brut (JSON tel qu’envoyé) avec le secret affiché une seule fois à la création de l’abonnement. Comparez avec Iwana-Signature. Répondez 2xx en moins de 15 secondes. Chaque événement est livré une seule fois — pas de nouvelle tentative automatique aujourd’hui. Les échecs sont visibles dans Paramètres → Développeurs ; pour rattraper un événement manqué, relisez périodiquement GET /appointments?from=&to= et GET /clients.

Erreurs

Toute erreur a la même forme, et porte l’identifiant de la requête qui l’a produite. Citez requestId quand vous nous écrivez au sujet d’un appel : il est aussi renvoyé en en-tête X-Request-Id sur toutes les réponses, succès compris.

{
  "error": {
    "code": "site_not_found",
    "message": "site_not_found",
    "requestId": "70b0b670-ecb1-4ceb-b8a0-9096b8b56ee3"
  }
}

code est stable et destiné au code appelant ; message est pour un humain et peut changer sans préavis. Ne faites pas de logique sur le message.

HTTPSignificationExemples
401Clé ou jeton absent, mal formé, expiré ou révoquéunauthorized, invalid_token
403Action refusée : rôle de l’utilisateur (jeton OAuth), clé de test en écriture, ou clé créée avant l’accès complet — rouvrez-la dans Paramètres → Développeurs et enregistrezforbidden, test_key_read_only
404Ressource introuvable dans l’organisation—
400Validation ou règle métierservice_required, invalid_range
429Budget de l’espace de travail épuisérate_limited

Limites de débit

Le budget appartient à l’espace de travail, et toutes ses clés API et jetons OAuth dépensent le même. Les personnes qui utilisent le tableau de bord ont chacune le leur : une intégration qui boucle ne peut pas bloquer son propre client.

FenêtrePar espace, clés et jetons confondusEn-têtes
1 minute600 requêtesX-RateLimit-Limit, -Remaining, -Reset (secondes)
24 heures50 000 requêtesX-RateLimit-Limit-Day, -Remaining-Day, -Reset-Day

Ce sont les valeurs par défaut ; l’équipe Iwana peut les changer pour toute la plateforme, lisez donc les en-têtes X-RateLimit-Limit* plutôt que de coder les nombres en dur. Au-delà de l’une des fenêtres, la réponse est 429 avec l’enveloppe habituelle, error.code = "rate_limited", et un en-tête Retry-After en secondes. Un plancher par IP de 1 000 requêtes/minute s’applique aussi avant authentification.

HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 12

{ "error": { "code": "rate_limited", "message": "…", "requestId": "…" } }

Rythmez-vous sur X-RateLimit-Remaining et reculez sur 429. Rafraîchissez les access tokens à l’heure, pas à chaque requête.

Versioning

Version actuelle : v1 (/api/v1/pro/…). Les changements incompatibles seront publiés sous /v2/ avec une période de chevauchement d’au moins 12 mois sur v1.

Les ajouts rétro-compatibles (nouveaux champs optionnels, nouveaux endpoints) peuvent arriver sans bump de version majeure. Traitez les champs inconnus comme normaux : votre client ne doit pas échouer parce que nous en avons ajouté un.

OpenAPI

Toute cette page existe aussi sous forme lisible par une machine. Collez cette URL dans Postman, Insomnia, Swagger UI ou un générateur de SDK — vous n’avez pas à recopier les schémas à la main.

curl https://www.iwana.site/api/v1/openapi.json

Ouvrir openapi.json

Le document est servi sans authentification : une spécification qu’il faut une clé pour lire est une spécification que personne n’évalue avant de s’inscrire. Il ne décrit que des formes — aucune donnée d’espace de travail n’y transite.

Prompts pour agents IA

Vous construisez l’intégration avec Claude, Cursor, Copilot ou un autre agent de code ? Ces prompts lui donnent le contrat exact de l’API pour qu’il n’ait pas à le deviner. Copiez, remplacez ce qui est entre chevrons, collez.

Commencez toujours par la fiche de contexte : elle tient dans un message système ou en tête de conversation, et chaque prompt de tâche s’appuie dessus.

Fiche de contexte — à coller avant toute tâche

Les faits que l’agent doit connaître pour ne rien inventer : URL, authentification, enveloppe d’erreur, limites, où lire le contrat complet.

Tu vas m'aider à intégrer l'API REST d'Iwana (SaaS de prise de rendez-vous pour artisans, France/UE). Voici le contrat ; ne suppose rien qui n'y figure pas — lis d'abord la spécification OpenAPI.

- Spécification OpenAPI (source de vérité pour chaque schéma) : https://www.iwana.site/api/v1/openapi.json
- Documentation humaine : https://www.iwana.site/developers/api
- URL de base : https://www.iwana.site/api/v1/pro — toutes les routes ci-dessous sont relatives à ce préfixe.
- Authentification : en-tête "Authorization: Bearer <jeton>". Deux formes de jeton, mêmes routes, mêmes permissions :
  - clé API d'espace de travail "iw_live_…" (ou "iw_test_…" en sandbox), créée dans Paramètres → Développeurs ;
  - access token OAuth 2.0 "iwa_at_…" obtenu par Authorization Code (autorisation : https://www.iwana.site/oauth/authorize ; token : POST https://www.iwana.site/api/v1/oauth/token en form-urlencoded ; identité : GET https://www.iwana.site/api/v1/oauth/userinfo). Access token 1 h, refresh token 3 ans et inchangé au refresh, PKCE S256 optionnel.
- Aucun en-tête x-workspace-id : le jeton porte l'espace de travail.
- Health-check : GET /integrations/me renvoie orgId, acteur, permissions, locale, fuseau.
- Une clé ou un jeton donne accès à toute cette API. Un 403 signifie que le rôle de l'utilisateur (jeton OAuth) ou une clé de test (lecture seule) refuse l'action.
- Erreurs : toujours { "error": { "code": "…", "message": "…", "requestId": "…" } }. "code" est stable, "message" ne l'est pas : ne jamais brancher de logique sur "message". X-Request-Id est présent sur toutes les réponses.
- Limites : 600 requêtes/minute et 50 000/jour par espace de travail, partagées entre toutes ses clés et jetons. Lire X-RateLimit-Remaining et X-RateLimit-Remaining-Day ; sur 429, respecter Retry-After (secondes).
- Idempotence : stocker la correspondance entre mes IDs et ceux d'Iwana via PUT /external-refs { provider, entityType, localId, externalId } et la résoudre avec GET /external-refs?provider=&entityType=&externalId= avant de créer.
- Dates en ISO 8601 UTC. Champs inconnus dans une réponse : les ignorer, jamais échouer.
- Webhooks entrants : POST JSON signé ; en-têtes Iwana-Signature (sha256=<HMAC-SHA256 du corps brut>), Iwana-Event, Iwana-Delivery. Vérifier la signature sur le corps BRUT, répondre 2xx en moins de 15 s, traiter en asynchrone. Une seule tentative de livraison, sans nouvelle tentative automatique : prévoir une réconciliation périodique via les endpoints de liste.
- Ne jamais logger une clé, un secret client ou un jeton.

Mon système à connecter : <CRM / ERP / outil>.

1. Synchronisation clients et rendez-vous avec une clé API

Une synchro bidirectionnelle, idempotente, qui respecte les limites de débit.

En t'appuyant sur la fiche de contexte Iwana ci-dessus, implémente une synchronisation bidirectionnelle entre <mon système> et Iwana avec une clé API d'espace de travail.

Exigences :
1. Au démarrage, appelle GET /integrations/me et arrête-toi avec un message clair si les permissions ne contiennent pas ce dont la synchro a besoin.
2. Lis le catalogue (GET /catalog/services) et l'équipe (GET /team) une fois et mets-les en cache pour mapper mes IDs de prestations et d'intervenants.
3. Pour chaque contact de <mon système> : résous d'abord GET /external-refs?provider=<mon-provider>&entityType=client&externalId=<id> ; s'il n'existe pas, POST /clients puis PUT /external-refs. Ne crée jamais deux fois le même contact.
4. Même schéma pour les rendez-vous (POST /appointments avec serviceId, clientId, startAt/endAt en ISO 8601 UTC, memberIds, customFields.<monId>).
5. Respecte le budget : lis X-RateLimit-Remaining, ralentis quand il descend sous 10 %, et sur 429 attends Retry-After avant de réessayer. Ne réessaie jamais un 400 ou un 403.
6. Journalise requestId de chaque erreur, jamais la clé.

Commence par lister les endpoints que tu vas appeler et dans quel ordre, puis implémente.

2. Implémenter « Sign in with IWANA » (OAuth 2.0)

Pour un produit que plusieurs entreprises installent : l’utilisateur autorise votre application, vous obtenez un jeton par espace de travail.

En t'appuyant sur la fiche de contexte Iwana ci-dessus, implémente « Sign in with IWANA » (Authorization Code avec secret client, PKCE S256 activé).

Contrat :
- Redirige l'utilisateur vers https://www.iwana.site/oauth/authorize?client_id=<CLIENT_ID>&redirect_uri=<URI enregistrée à l'identique>&response_type=code&state=<aléatoire>&code_challenge=<S256>&code_challenge_method=S256
- Au retour sur redirect_uri : vérifie state ; si ?error=access_denied, affiche un message et n'échange rien. Le code (iwa_ac_…) vit 60 s et ne sert qu'une fois.
- Échange : POST https://www.iwana.site/api/v1/oauth/token, Content-Type application/x-www-form-urlencoded, grant_type=authorization_code&code=…&redirect_uri=<même valeur>&client_id=…&client_secret=…&code_verifier=…
  Réponse : { access_token: "iwa_at_…", token_type: "Bearer", expires_in: 3600, refresh_token: "iwa_rt_…", scope: "…" }
- Identité : GET https://www.iwana.site/api/v1/oauth/userinfo avec le bearer → { sub, email, name, org_id, org_name, role, scope }. Lie mon compte utilisateur à la paire (sub, org_id).
- Refresh : grant_type=refresh_token&refresh_token=…&client_id=…&client_secret=… → nouvel access_token, le refresh_token reste le même. Rafraîchis à l'expiration (1 h), pas à chaque requête.
- Erreurs du token endpoint : { error, error_description } avec invalid_request, invalid_client (401), invalid_grant, unsupported_grant_type. Sur invalid_grant au refresh, le grant a été révoqué ou a expiré : marque la connexion comme à refaire et renvoie l'utilisateur vers l'autorisation.
- Un 401 invalid_token sur une route de l'API avec un access token non expiré signifie que l'espace de travail a révoqué l'application : même traitement.
- Conserve access_token, refresh_token, expires_at, sub et org_id par connexion ; ne logge jamais un jeton ni le secret client.

Gère chaque cas ci-dessus : state invalide, access_denied, expiration du code, expiration du jeton et révocation.

3. Recevoir les webhooks et vérifier la signature

Un endpoint HTTPS qui vérifie le HMAC sur le corps brut, répond vite et traite en arrière-plan.

En t'appuyant sur la fiche de contexte Iwana ci-dessus, implémente un récepteur de webhooks Iwana.

Contrat de livraison :
- POST JSON sur mon URL HTTPS. En-têtes : Iwana-Signature: sha256=<HMAC-SHA256 hex du corps brut avec mon secret d'abonnement>, Iwana-Event: <type>, Iwana-Delivery: <uuid unique par tentative>.
- Corps : { "event": "appointment.created", "orgId": "…", "data": { "id": "…", "startAt": "…", "status": "…" }, "occurredAt": "…" }
- Événements : appointment.created, appointment.updated, appointment.cancelled, client.created, client.updated.
- Une seule tentative par événement, timeout 15 s, pas de nouvelle tentative automatique : un événement manqué doit être rattrapé par réconciliation.

Exigences :
1. Lis le corps BRUT (avant tout parsing JSON) et compare le HMAC en temps constant ; réponds 401 si la signature ne correspond pas.
2. Réponds 2xx immédiatement après avoir mis l'événement en file ; traite en asynchrone.
3. Traite chaque Iwana-Delivery une seule fois (stocke les IDs traités) par précaution.
4. Sur appointment.* et client.*, recharge l'objet complet via GET /appointments/{id} ou GET /clients/{id} plutôt que de faire confiance au payload partiel.
5. Ne logge jamais le secret d'abonnement.
6. Ajoute une réconciliation périodique (GET /appointments?from=&to=, GET /clients) pour rattraper les événements manqués.

4. Construire un client à partir de la spécification OpenAPI

Ne recopiez pas les schémas : l’agent lit la spécification publique et en construit le client.

En t'appuyant sur la fiche de contexte Iwana ci-dessus, construis un client pour l'API PRO d'Iwana à partir de sa spécification OpenAPI.

1. Télécharge https://www.iwana.site/api/v1/openapi.json et ne garde que les chemins qui commencent par /api/v1/pro/ (plus /api/v1/oauth/token et /api/v1/oauth/userinfo si je fais de l'OAuth).
2. Dérive chaque type de requête et de réponse de la spécification plutôt que de les écrire à la main, pour pouvoir régénérer le client quand la spécification change.
3. Le client doit : injecter "Authorization: Bearer <jeton>", lire X-RateLimit-Remaining et attendre Retry-After sur 429, transformer l'enveloppe { error: { code, message, requestId } } en une erreur typée portant code et requestId, et ignorer les champs inconnus.
4. Vérifie le client contre l'API réelle avec GET /integrations/me et une clé iw_test_ que je te fournis.

Montre-moi d'abord la liste des opérations retenues, puis le client.

Prêt à intégrer ?

Créez votre première clé dans le tableau de bord, ou écrivez-nous pour enregistrer une application OAuth ou obtenir un accès sandbox dédié.

Vos cookies

Nous utilisons des cookies strictement nécessaires au fonctionnement du service. Avec votre accord, nous activons aussi des cookies de supervision (Sentry) pour diagnostiquer les incidents. En savoir plus dans notre politique cookies.