Ajouter un service SaaS (exemple avec sci-facturation)
Cette page déroule de bout en bout l'ajout d'un webservice appelant. Elle prend sci-facturation pour exemple : remplacez son nom, son préfixe et l'URL de son webhook par ceux de votre propre service. Comptez une quinzaine de minutes, hors saisie du catalogue.
Les étapes se suivent dans l'ordre de leurs dépendances : chacune a besoin de la précédente.
Étape 1 : créer l'utilisateur de service côté Dolibarr
Dans Accueil > Utilisateurs et groupes > Nouvel utilisateur, créez un
utilisateur dédié, par exemple svc-scifacturation, avec :
- un mot de passe aléatoire que personne n'utilise, car cet utilisateur ne se connecte jamais à l'interface ;
- les droits sur les tiers (lecture et création), sur les produits et services, sur les commandes et sur les factures. C'est en effet lui qui porte les écritures comptables produites par l'API.
Pourquoi un utilisateur dédié ? D'abord parce qu'un compte administrateur ne suffit pas : Dolibarr vérifie les droits bruts, module par module, au moment de valider une commande ou une facture. Ensuite parce qu'un utilisateur partagé entre plusieurs services rendrait le journal comptable illisible.

Étape 2 : créer le client OAuth chez smartauth
Ouvrez la liste des clients OAuth par Accueil > Configuration > Modules > smartauth (icône d'engrenage), onglet OAuth, puis Nouveau client OAuth.

Renseignez le formulaire ainsi :
| Champ | Valeur | Pourquoi |
|---|---|---|
| Nom | SCIfacturation M2M |
Un libellé libre, destiné à vous seul |
| Type | Confidentiel | Obligatoire pour le flux machine à machine (M2M) |
| PKCE obligatoire | Non | PKCE protège le flux à code d'autorisation et ne s'applique pas au flux M2M |
| Grants autorisés | client_credentials seul | Un seul webservice, donc un seul moyen de s'authentifier |
| Redirect URIs | https://scifacturation.example/callback |
Jamais utilisé par ce flux ; le champ accepte une valeur de remplissage |
| Utilisateur de service | svc-scifacturation |
L'utilisateur créé à l'étape 1 |
| Durée access token | 3600 (1 heure) | Le webservice met le jeton en cache et en redemande un à l'expiration |
| Durée refresh token | Valeur par défaut | Sans importance : ce flux n'émet jamais de refresh token |
| Scopes | Rien à cocher | Le périmètre du service est borné par sa fiche dans smartmakersaasbilling, et non par des scopes |
| Statut | Actif |
À l'enregistrement, l'écran affiche une seule fois le client_id et le
secret en clair : copiez-les immédiatement. Le secret n'est conservé que
sous forme hachée, il ne pourra donc plus être relu, seulement régénéré.
Puis, sur cette même fiche, cliquez sur "Autoriser sur l'API interne". Les
routes que les modules exposent en oauth2 sont fermées par défaut ; sans
cela, n'importe quel client OAuth de l'instance pourrait appeler n'importe
quelle route de n'importe quel module avec les droits de son utilisateur de
service.
C'est l'étape la plus facile à oublier, parce que son absence ne ressemble pas
à un oubli : le webservice obtient son jeton sans difficulté, la route est bien
trouvée, et l'appel répond malgré tout 401. Seul le journal Dolibarr le dit,
sous la forme SMARTAUTH_API_AUDIENCE not set - refusing <client_id>. La fiche
du produit autorisé, à l'étape suivante, signale le cas et renvoie ici.
Étape 3 : créer la fiche produit autorisé
Revenez dans smartmakersaasbilling, onglet Produits SaaS autorisés, puis Nouveau produit autorisé :
| Champ | Valeur |
|---|---|
| Réf. | scifacturation, stable et jamais renommée par la suite |
| Libellé | Celui de votre choix |
| Client OAuth smartauth | Le client créé à l'étape 2 |
| Préfixe de références produit | P-SCIFAC- |
| URL du webhook de règlement | https://<votre-webservice>/api/v1/webhooks/dolibarr |
| Actif | Oui |

Le secret de signature du webhook est généré à l'enregistrement, puis affiché sur la fiche : transmettez-le au webservice par un canal sûr. C'est avec lui qu'il vérifiera la signature des notifications de règlement.
Tant qu'aucune fiche active n'existe, tout appel à l'API est rejeté : l'API refuse par défaut, ce qui rend cette étape indispensable.
Étape 4 : créer le catalogue
Le service a besoin d'une fiche produit par acte facturable. Pour sci-facturation, cela donne :
P-SCIFAC-ABO-T1àP-SCIFAC-ABO-T4pour les abonnements annuels par tranche ;P-SCIFAC-MES-T1àP-SCIFAC-MES-T4pour les mises en service au prorata ;P-SCIFAC-MUTpour le forfait de mutation.
Seul le préfixe est configuré, à l'étape 3 ; le suffixe encode la nature de l'acte et la tranche, et c'est le webservice qui le déduit. Ne recopiez pas ces références à la main : un caractère erroné passe inaperçu jusqu'au premier acte à commander, où la commande est refusée faute de produit correspondant.
Demandez plutôt au webservice son gabarit de catalogue, un fichier JSON qui
énumère les fiches qu'il attend. L'annexe en décrit le format ; du côté de
sci-facturation, la commande est php artisan sci:billing-catalogue-template.
Sur la fiche du produit autorisé, le bouton Importer un gabarit de catalogue
affiche alors, ligne par ligne, ce qu'il créerait, avec le prix proposé par le
webservice, que vous pouvez modifier avant création.
Trois règles gouvernent cet écran :
- une fiche déjà présente n'est jamais modifiée : le prix relève d'une décision commerciale prise ici, et un gabarit capable de l'écraser ferait du fichier la source du barème. Un nouvel import ne peut donc rien détériorer ;
- seules les références portant le préfixe du client sont reprises ;
- rien ne transite par l'API : le fichier se dépose à la main, si bien qu'un jeton compromis reste incapable de créer la moindre fiche produit.
Les fiches créées sont de type service et actives à la vente. Vérifiez les prix avant de valider : un service sans prix est refusé à la facturation, délibérément, et le webservice le signale également de son côté.
Un service qui ne produit pas de gabarit se saisit à la main, dans Produits et services, avec les mêmes caractéristiques.
Étape 5 : vérifier la configuration de l'instance
Ouvrez l'onglet Configuration du module : il signale de lui-même ce qui manque, à savoir
- les constantes
PAYMENT_SECURITY_TOKENetPAYMENT_SECURITY_TOKEN_UNIQUEdu cœur de Dolibarr. Sans elles, le lien de paiement est construit sans sa clé de sécurité et la page de paiement refuse la commande ; - l'absence de module de paiement en ligne, qu'il s'agisse de stripe ou de stancer.

Étape 6 : remettre les informations au webservice
Le service appelant reçoit quatre informations, et pas une de plus :
- le
client_idet le secret OAuth de l'étape 2 ; - le secret de signature du webhook de l'étape 3 ;
- l'adresse de l'API :
https://<votre-dolibarr>/custom/smartmakersaasbilling/pwa/api.php; - l'adresse du endpoint de jeton, qui ne se trouve pas sous cet hôte :
https://<votre-portail-sso>/oauth/token.
Ces deux dernières adresses constituent le second piège du parcours. Le portail
SSO de smartauth est exposé sur un sous-domaine dédié, dont la racine web est
son propre répertoire public/, et il ne reconnaît un endpoint OAuth que sur
un chemin absolu commençant par /oauth/. Toute adresse de la forme
https://<votre-dolibarr>/custom/smartauth/... répond 404, en page HTML, sans
autre explication. La valeur exacte se lit sur l'instance :
curl -s https://<votre-portail-sso>/.well-known/openid-configuration | jq .token_endpoint
Deux secrets circulent donc, et ils sont différents : celui de l'étape 2 sert à obtenir un jeton, celui de l'étape 3 à vérifier la signature des notifications de règlement. Les confondre côté webservice fait rejeter chaque règlement en 401 ; l'accès du client ne se rouvre alors qu'au prochain rapprochement, au lieu de la minute qui suit son paiement.
Le webservice obtient ses jetons contre le couple client_id/secret, met chaque jeton en cache pour sa durée de vie diminuée d'une marge, et vérifie la signature des notifications avec le secret de webhook.
Vérifier que tout fonctionne
Le test le plus simple consiste à lire le catalogue. Avant la création de la fiche de l'étape 3, l'appel est refusé ; après, il renvoie les fiches produit du préfixe du service ainsi que la précision décimale des prix. Le webservice peut également dérouler son propre écran de vérification de catalogue sur ce même appel.
En cas de refus
- Tout appel refusé (403) : aucune fiche active ne lie ce client OAuth. Revenez à l'étape 3, ou vérifiez que la fiche est bien active.
- Refus d'authentification (401) : le couple client_id/secret est erroné, ou le client OAuth est inactif, ou il n'est pas confidentiel.
- 401 alors que le jeton a bien été délivré : le client n'est pas autorisé
sur l'API interne. C'est le cas le plus déroutant, puisque tout fonctionne
jusqu'au dernier moment. Le journal Dolibarr porte alors
SMARTAUTH_API_AUDIENCE not setounot in SMARTAUTH_API_AUDIENCE allow-list. Retournez sur la fiche du client OAuth (étape 2) et cliquez sur Autoriser sur l'API interne. - 404 en page HTML sur la demande de jeton : l'adresse du endpoint de jeton pointe sous l'hôte Dolibarr au lieu du portail SSO (étape 6).
- Chaque règlement rejeté en 401 côté webservice : celui-ci vérifie la signature avec le secret OAuth au lieu du secret de webhook (étape 6).
- Erreur de droits à la validation d'une commande : l'utilisateur de service ne dispose pas des droits sur les tiers, les produits, les commandes et les factures. Revenez à l'étape 1.
- Commande refusée faute de produit correspondant : une référence a été recopiée de travers, ou la fiche n'a jamais été créée. L'import de gabarit de l'étape 4 répond précisément à ce problème, et le webservice sait indiquer ce qui manque.
Annexe : le format du gabarit de catalogue
Cette annexe s'adresse à qui développe le webservice appelant : elle décrit le fichier qu'importe l'étape 4, et qu'il vous revient de produire.
{
"format": "smartmakersaasbilling.catalogue",
"version": 1,
"prefix": "P-SCIFAC-",
"products": [
{
"ref": "P-SCIFAC-ABO-T1",
"label": "Abonnement annuel, 1re location",
"unit": "an",
"type": "service",
"vat_rate": "20",
"suggested_price_ht": "24.90",
"price_source": "default"
}
]
}
| Champ | Obligatoire | Ce qu'il contient |
|---|---|---|
format |
oui | Exactement smartmakersaasbilling.catalogue, faute de quoi le fichier est refusé |
version |
oui | 1 à ce jour. Une version supérieure est refusée, car un fichier plus récent pourrait porter des champs que ce module laisserait tomber en silence |
prefix |
non | Comparé au préfixe du produit autorisé ; s'il en diffère, rien n'est importé |
products[].ref |
oui | La référence complète. Si elle sort du préfixe du client, la ligne est écartée et l'écran le signale. Lettres, chiffres, tirets, tirets bas et points |
products[].label |
non | Le libellé de la fiche. À défaut, la référence en tient lieu |
products[].suggested_price_ht |
non | Une proposition, affichée et modifiable avant création. En son absence, la ligne demeure et le prix est créé à zéro |
products[].vat_rate |
non | Le taux appliqué à la création. En son absence, zéro |
products[].unit |
non | Ignoré à la création, et ce délibérément : Dolibarr note les durées dans sa propre syntaxe (1y), qui vous reviendrait ensuite comme unité d'affichage. Conservez votre libellé de votre côté |
products[].type |
non | Toutes les fiches sont créées en service |
products[].price_source |
non | Informatif |
Deux limites encadrent l'import : 100 lignes par fichier au maximum, et les doublons de référence sont écartés, l'écran le mentionnant. Un fichier dont aucune ligne n'est exploitable est refusé en bloc, plutôt qu'importé à moitié.
Rappelons enfin la règle essentielle : une fiche déjà présente n'est jamais mise à jour. Votre gabarit ne peut donc pas devenir la source du barème, et le prix reste décidé dans Dolibarr.