Saasy

Client Portal

Affiche des champs personnalises (usage, texte, nombre, date, selection, action) sur les fiches de tes utilisateurs et entreprises depuis le dashboard Saasy.

Client Portal

Le Client Portal te permet d'afficher des donnees metier sur les fiches de tes utilisateurs dans le dashboard Saasy. Usage API, plan actif, date d'expiration, boutons d'action : tu decides ce qui est pertinent pour ton equipe support.

Deux modes d'alimentation sont disponibles :

  • Push : ton application envoie les donnees a Saasy via l'API portal.
  • Pull : Saasy interroge ton API en temps reel lorsqu'un collaborateur ouvre une fiche utilisateur.

SSO obligatoire

Le Client Portal ne peut etre active que si ton application est en mode SSO. L'externalId du token SSO est utilise comme cle de correspondance. Si tu n'as pas encore configure le SSO, consulte le guide Authentification SSO et active le mode SSO dans Parametres > SSO avant de configurer le portal.

Modes de fonctionnement

Le portal supporte deux modes :

ModeDescription
Users uniquementChaque utilisateur est un tenant independant. Les champs custom s'affichent sur la fiche user.
Entreprises et usersLes utilisateurs sont rattaches a des entreprises. Tu peux definir des champs custom sur les fiches user ET sur les fiches entreprise.

Types de champs

TypeDescriptionExemple
usage_barBarre de progression avec valeur courante / max150 / 500 appels API
textTexte libreStatut: Actif
numberNombre formate42 projets
datetimeDate/heure localiseeExpire le 15/03/2026
selectMenu deroulant editablePlan: free / starter / growth / scale
actionBouton qui declenche un webhookReinitialiser le mot de passe

Valeurs illimitees

Pour les barres d'utilisation (usage_bar), une valeur max de -1 est interpretee comme illimitee. La barre affiche 150 / ∞ appels API au lieu d'un nombre negatif. Utile pour les plans sans limites.

Type select : champs editables

Le type select permet a tes collaborateurs de modifier une valeur directement depuis la fiche utilisateur. Par exemple, changer le plan d'un client.

Configuration :

  1. Ajoute un champ de type Selection.
  2. Renseigne les options (une par ligne) : free, starter, growth, scale.
  3. Un bouton Sauvegarder apparait automatiquement sur la fiche lorsqu'une valeur est modifiee.

Lorsqu'un collaborateur clique sur Sauvegarder, Saasy :

  1. Met a jour les customAttributes du TrackedUser en base.
  2. Si le Pull mode est active, pousse la modification vers ton API via PATCH {apiBaseUrl}/users/{externalId}/field avec { "key": "plan", "value": "growth" }.

Configuration

Activer le portal

  1. Va dans Parametres > SSO de ton app.
  2. Scrolle jusqu'a la section Client Portal.
  3. Active le toggle.
  4. Choisis le mode : Users uniquement ou Entreprises et users.

Configurer les champs

  1. Clique sur Ajouter un champ dans la section User Fields (ou Company Fields).
  2. Remplis la cle API (identifiant unique, ex: api_calls), le label, et le type.
  3. Pour un champ usage_bar, renseigne l'unite et la cle max (ex: api_calls_max).
  4. Pour un champ select, renseigne les options (une par ligne).
  5. Pour un champ action, renseigne l'URL du webhook et le texte du bouton.
  6. Enregistre.

Recuperer le token portal

Le Portal Token est affiche dans la section configuration. Copie-le et stocke-le dans les variables d'environnement de ton serveur. Ce token authentifie les appels API du portal.

Configurer le Pull mode (optionnel)

Si tu souhaites que Saasy recupere les donnees en temps reel depuis ton API :

  1. Renseigne l'URL de ton API dans le champ dedie (ex: https://mon-app.com/api/portal).
  2. Clique sur Enregistrer.
  3. Ton API doit exposer les endpoints decrits dans la section Pull mode ci-dessous.

Push mode (defaut)

Le push mode est le fonctionnement classique : ton serveur envoie les donnees a Saasy lorsqu'elles changent.

API Reference

Toutes les routes du portal sont authentifiees via le header X-Portal-Token.

Token serveur

Le portal token ne doit jamais etre expose cote client. Utilise-le uniquement depuis ton serveur backend.

Mettre a jour les champs d'un utilisateur

Pousse les valeurs des champs custom pour un utilisateur identifie par son externalId (l'id que tu passes dans le token SSO).

curl -X PATCH https://api.saasy.fr/api/portal/users/user_123 \
  -H "X-Portal-Token: pt_votre_token" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "api_calls": 150,
      "api_calls_max": 500,
      "plan": "Enterprise",
      "trial_ends_at": "2026-04-15T00:00:00Z"
    }
  }'

Creer ou mettre a jour une entreprise

Cree l'entreprise si elle n'existe pas, ou la met a jour.

curl -X PUT https://api.saasy.fr/api/portal/companies/company_456 \
  -H "X-Portal-Token: pt_votre_token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "domain": "acme.com",
    "customAttributes": {
      "seats_used": 12,
      "seats_max": 25,
      "plan": "Business"
    }
  }'

Mettre a jour les champs d'une entreprise

curl -X PATCH https://api.saasy.fr/api/portal/companies/company_456 \
  -H "X-Portal-Token: pt_votre_token" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": {
      "seats_used": 15,
      "mrr": 299
    }
  }'

Assigner un utilisateur a une entreprise

curl -X PATCH https://api.saasy.fr/api/portal/users/user_123/company \
  -H "X-Portal-Token: pt_votre_token" \
  -H "Content-Type: application/json" \
  -d '{
    "companyExternalId": "company_456"
  }'

Pull mode

Le pull mode permet a Saasy d'interroger ton API en temps reel pour recuperer les donnees utilisateur, au lieu d'attendre que tu les pousses.

Principe

Collaborateur ouvre une fiche utilisateur
  → Saasy appelle GET {apiBaseUrl}/users/{externalId}
    → Ton API retourne { customAttributes: { ... } }
  → Saasy affiche les donnees fraiches et les met en cache

Si ton API est indisponible (timeout 5s, erreur), Saasy affiche les dernieres donnees en cache.

Configuration

Renseigne l'URL de ton API dans Parametres > SSO > Client Portal (ex: https://mon-app.com/api/portal) et clique sur Enregistrer.

Endpoints a implementer

Ton API doit exposer les endpoints suivants :

Recuperer les donnees d'un utilisateur

Reponse attendue :

{
  "customAttributes": {
    "plan": "growth",
    "api_calls": 1523,
    "api_calls_max": 5000,
    "trial_ends_at": "2026-06-15T00:00:00Z"
  }
}

Les cles dans customAttributes doivent correspondre aux cles API des champs configures dans le portal.

Recevoir une modification de champ (optionnel)

Quand un collaborateur modifie un champ select et clique sur Sauvegarder, Saasy envoie la modification a ton API :

{
  "key": "plan",
  "value": "growth"
}

Ce endpoint est optionnel. Si tu ne l'implementes pas, la valeur est quand meme sauvegardee dans Saasy.

Verification de signature

Chaque requete pull de Saasy inclut des headers de signature :

HeaderDescription
X-Portal-TokenLe portal token de l'app
X-Saasy-SignatureHMAC-SHA256 de {timestamp}:{path}
X-Saasy-TimestampTimestamp ISO de la requete
const crypto = require('crypto');

function verifySaasySignature(req, portalToken) {
  const signature = req.headers['x-saasy-signature'];
  const timestamp = req.headers['x-saasy-timestamp'];
  const path = req.path; // ex: /users/user_123

  const expected = crypto
    .createHmac('sha256', portalToken)
    .update(`${timestamp}:${path}`)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Pour les requetes PATCH (modification de champ), la signature est calculee sur {timestamp}:PATCH:{path}.

Creation et rattachement automatique via SSO

En mode Entreprises et users, Saasy cree et rattache automatiquement les entreprises a partir des donnees de ton token JWT SSO. Tu n'as pas besoin de creer les entreprises au prealable.

A chaque connexion SSO, si le JWT contient un champ companies avec au moins un objet comportant externalId et name, Saasy :

  1. Cree ou met a jour l'entreprise dans sa base (upsert sur l'externalId).
  2. Rattache l'utilisateur a la premiere entreprise de la liste.
const ssoToken = jwt.sign({
  id: user.id,
  email: user.email,
  name: user.name,
  companies: [
    {
      externalId: user.company.id,   // obligatoire
      name: user.company.name,        // obligatoire
      domain: user.company.domain,    // optionnel
    },
  ],
}, process.env.SAASY_SSO_KEY, { expiresIn: '1h' });

Format du champ companies

Utilise le format objet avec au minimum externalId et name pour que Saasy puisse creer automatiquement l'entreprise. Le format string (["company_456"]) permet uniquement le rattachement a une entreprise deja existante.

Recuperer le profil utilisateur (widget)

Si tu as besoin de recuperer les informations de l'utilisateur connecte et sa societe depuis le widget ou ton front-end, utilise la route suivante :

Cette route necessite un token widget (obtenu via l'authentification SSO) dans le header Authorization: Bearer <token>.

Reponse

{
  "data": {
    "id": "...",
    "externalId": "user_123",
    "email": "john@acme.com",
    "name": "John Doe",
    "authMethod": "sso",
    "customAttributes": { "plan": "Enterprise" },
    "companies": ["company_456"],
    "company": {
      "id": "...",
      "externalId": "company_456",
      "name": "Acme Corp",
      "domain": "acme.com",
      "customAttributes": { "seats_used": 12, "seats_max": 25 }
    },
    "lastSeenAt": "2026-03-11T10:00:00.000Z",
    "createdAt": "2026-01-15T08:30:00.000Z"
  }
}

Si l'utilisateur n'est rattache a aucune entreprise, le champ company est absent de la reponse.

Exemples d'integration

Push mode

const PORTAL_TOKEN = process.env.SAASY_PORTAL_TOKEN;
const SAASY_API = 'https://api.saasy.fr/api';

// Mettre a jour l'usage d'un user apres chaque appel API
async function syncUserUsage(userId, apiCalls, apiCallsMax) {
  await fetch(`${SAASY_API}/portal/users/${userId}`, {
    method: 'PATCH',
    headers: {
      'X-Portal-Token': PORTAL_TOKEN,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      fields: { api_calls: apiCalls, api_calls_max: apiCallsMax },
    }),
  });
}

Pull mode

const express = require('express');
const crypto = require('crypto');
const app = express();

const PORTAL_TOKEN = process.env.SAASY_PORTAL_TOKEN;

// Middleware de verification de signature
function verifySaasy(req, res, next) {
  const token = req.headers['x-portal-token'];
  if (token !== PORTAL_TOKEN) return res.status(401).json({ error: 'Unauthorized' });
  next();
}

// GET /api/portal/users/:externalId — retourne les customAttributes
app.get('/api/portal/users/:externalId', verifySaasy, async (req, res) => {
  const user = await db.users.findById(req.params.externalId);
  if (!user) return res.status(404).json({ error: 'Not found' });

  res.json({
    customAttributes: {
      plan: user.plan,
      api_calls: user.usage.apiCalls,
      api_calls_max: user.limits.apiCalls,
      trial_ends_at: user.trialEndsAt?.toISOString() || null,
    },
  });
});

// PATCH /api/portal/users/:externalId/field — recoit une modification
app.patch('/api/portal/users/:externalId/field', verifySaasy, express.json(), async (req, res) => {
  const { key, value } = req.body;
  const user = await db.users.findById(req.params.externalId);
  if (!user) return res.status(404).json({ error: 'Not found' });

  // Applique la modification (ex: changement de plan)
  if (key === 'plan') user.plan = value;
  await user.save();

  res.json({ success: true });
});

Webhooks d'action

Les champs de type action declenchent un webhook POST vers l'URL configuree lorsqu'un collaborateur clique sur le bouton dans le dashboard.

Format du payload

{
  "action": "reset_password",
  "fieldId": "abc123",
  "user": {
    "id": "...",
    "externalId": "user_123",
    "email": "john@acme.com",
    "name": "John Doe"
  },
  "company": null,
  "timestamp": "2026-03-09T14:30:00.000Z"
}

Verification HMAC

Chaque webhook est signe avec le portal token comme secret HMAC-SHA256. La signature est envoyee dans le header X-Saasy-Signature.

const crypto = require('crypto');

function verifyWebhook(body, signature, portalToken) {
  const expected = crypto
    .createHmac('sha256', portalToken)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Dans ton handler Express
app.post('/webhook/saasy', express.text({ type: '*/*' }), (req, res) => {
  const signature = req.headers['x-saasy-signature'];
  if (!verifyWebhook(req.body, signature, process.env.PORTAL_TOKEN)) {
    return res.status(401).send('Invalid signature');
  }
  const payload = JSON.parse(req.body);
  // Traite l'action...
  res.sendStatus(200);
});

Securite

  • Token serveur : le portal token ne doit jamais etre expose cote client. Stocke-le dans tes variables d'environnement.
  • HMAC-SHA256 : les webhooks d'action et les requetes pull sont signes. Verifie toujours la signature avant de traiter le payload.
  • SSRF : les URLs de webhook et l'API Base URL ne peuvent pas pointer vers des adresses IP privees en production (localhost, 10.x, 192.168.x, etc.).
  • Rate limiting : les routes portal sont limitees a 1000 requetes par 15 minutes par IP.
  • Taille : chaque push de customAttributes est limite a 64 KB. Maximum 50 champs par entite.
  • Timeout pull : les requetes pull vers ton API ont un timeout de 5 secondes. Si ton API ne repond pas a temps, les donnees en cache sont utilisees.