Intégration de l'API

Guide pratique pour intégrer l’API GraphQL de Gibbond, avec des exemples à copier-coller pour un service interne, une app mobile ou web, ou un autre canal de vente. Consulte la référence de l’API pour le schéma complet.

Sur cette page
⚡

Forfait Pro. Crée une clé API dans la page Clés API de l’admin Gibbond, puis conserve-la dans une variable d’environnement côté serveur. Les exemples ci-dessous supposent que GIBBOND_API_KEY contient une clé avec l’autorisation WRITE.

Comment les éléments s’articulent

Il existe deux identifiants : choisir le bon est essentiel :

  • Clé API (glk_live_…) — identifie ton backend. Elle peut lire et modifier les données de tous les membres : elle doit donc rester sur ton serveur. Utilise-la pour les échanges entre serveurs et pour créer des jetons client.
  • Jeton client — un identifiant temporaire limité à un seul client. Ton backend le crée avec la clé API et le transmet à un client (navigateur ou app). Celui-ci ne peut consulter que les données de ce client et ne peut agir que pour lui ; le jeton peut donc être envoyé à l’appareil en toute sécurité.

Choisis ton mode d’intégration

Tu développes…UtiliseAller à
Un traitement interne, une synchronisation ERP ou un backend headlessClé API directementDepuis ton backend
Une app mobile ou monopage qui affiche les points d’un membreClé API sur ton serveur → jeton client sur l’appareilDans une app
Un POS, une marketplace ou un checkout headless qui doit attribuer des pointsClé API, awardPointsForOrderDepuis un autre canal

Fonction utilitaire pour les requêtes

Toutes les requêtes suivent la même structure : encapsule-les dans une fonction utilitaire. Cet exemple Node utilise la fonction intégrée fetch (Node 18+) :

// gibbond.js
const ENDPOINT = "https://app.gibbond.com/api/v1/graphql";

export async function gibbond(query, variables, token) {
  const res = await fetch(ENDPOINT, {
    method: "POST",
    headers: {
      "Authorization": "Bearer " + token,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query, variables }),
  });

  if (res.status === 429) {
    const retryAfter = Number(res.headers.get("Retry-After") || 1);
    const err = new Error("Rate limited");
    err.retryAfter = retryAfter;
    throw err;
  }

  const json = await res.json();
  if (json.errors && json.errors.length) {
    const code = json.errors[0].extensions && json.errors[0].extensions.code;
    throw new Error(code + ": " + json.errors[0].message);
  }
  return json.data;
}

Modèle 1 — Depuis ton backend (serveur à serveur)

Avec une clé WRITE, tu peux agir pour n’importe quel membre de ta boutique. Inscris un client au programme de fidélité, consulte ses données et attribue-lui des points :

import { gibbond } from "./gibbond.js";
const KEY = process.env.GIBBOND_API_KEY;
const id = "gid://shopify/Customer/123456789";

// Enroll a customer (idempotent — safe to call again).
await gibbond(
  "mutation($id: ID!, $email: String) { enrollCustomer(customerId: $id, email: $email) { ok customer { enrolled } } }",
  { id, email: "ada@example.com" },
  KEY,
);

// Read their balance and tier.
const { customer } = await gibbond(
  "query($id: ID!) { customer(id: $id) { balance pending lifetime tier { name multiplier } } }",
  { id },
  KEY,
);

// Grant points. idempotencyKey makes retries safe — the same key never double-applies.
await gibbond(
  "mutation($id: ID!, $pts: Int!, $k: String) { awardPoints(customerId: $id, points: $pts, reason: \"Newsletter signup\", idempotencyKey: $k) { ok pointsApplied balance { balance } } }",
  { id, pts: 100, k: "newsletter-123456789" },
  KEY,
);
ℹ️

Une valeur négative pour awardPoints et la mutation clawbackPoints nécessitent une clé ADMIN. Réserve ces opérations à un identifiant distinct et bien protégé.

Modèle 2 — Dans une app mobile ou web

N’intègre jamais la clé API dans une app. Expose plutôt un petit point de terminaison sur ton serveur, qui crée un jeton client pour l’utilisateur connecté. L’app pourra alors communiquer directement avec Gibbond grâce à ce jeton.

D’où vient le GID du client ?

Un jeton client est associé au GID transmis à mintCustomerToken : tu dois donc le créer pour le bon client. Gibbond authentifie ton backend via la clé API, mais n’authentifie pas le client final. Avant de créer le jeton, ton serveur doit déjà connaître et pouvoir vérifier l’identité du client Shopify connecté. Voici trois façons de l’établir, par ordre de préférence :

Comment ton app authentifie le clientOrigine du GID
Shopify Customer Account API (recommandée)Fais connecter le client via la Customer Account API de Shopify, basée sur OAuth. Ton backend échange le jeton obtenu contre l’identité du client vérifié et récupère son GID. Shopify garantit cette identité de bout en bout.
Une interface native ShopifySi l’expérience se déroule dans Shopify (via un app embed de thème ou une extension d’interface de compte client), Shopify fournit déjà l’identité signée — avec logged_in_customer_id de l’app proxy ou sub du jeton de session du compte client. Lis cette valeur côté serveur et utilise-la comme GID.
Ton propre système de connexionUn canal avec ses propres comptes (app personnalisée ou POS) associe l’utilisateur au GID d’un client Shopify à partir d’une correspondance enregistrée lors de la création du client. Tu garantis ce lien : protège donc soigneusement ce mécanisme.
✅

Recommandation : Customer Account API. Pour une boutique headless ou une app mobile, authentifie les clients avec la Customer Account API de Shopify et déduis le GID à partir du jeton vérifié par Shopify. Toute la chaîne — client → identité vérifiée par Shopify → backend → jeton client créé — est ainsi fiable dès la conception, sans correspondance de GID à sécuriser. Le middleware requireAuth ci-dessous est précisément l’endroit où effectuer cette vérification.

ℹ️

Si ton interface s’exécute déjà dans une session Shopify (bloc de thème, de paiement ou de compte client), tu n’as généralement pas besoin de cette API : les blocs d’app de la boutique et l’intégration du compte client vérifient l’identité Shopify pour toi. Utilise les jetons client lorsque tu es en dehors de ce contexte.

Sur ton serveur

Ton propre système d’authentification détermine quel client Shopify est connecté (son GID). Crée un jeton pour ce client :

import express from "express";
import { gibbond } from "./gibbond.js";

const app = express();

// requireAuth is YOUR session middleware; it sets req.customerGid.
app.post("/loyalty/session", requireAuth, async (req, res) => {
  const data = await gibbond(
    "mutation($id: ID!) { mintCustomerToken(customerId: $id, ttlSeconds: 900) { token expiresAt } }",
    { id: req.customerGid },
    process.env.GIBBOND_API_KEY, // WRITE key, server-side only
  );
  // Hand the app the token, when it expires, and the id it is bound to.
  res.json({
    token: data.mintCustomerToken.token,
    expiresAt: data.mintCustomerToken.expiresAt,
    customerId: req.customerGid,
  });
});

Dans l’app

Récupère un jeton depuis ton serveur, puis appelle directement Gibbond. La requête me renvoie le client associé au jeton : aucun identifiant n’est nécessaire.

// 1. Get a short-lived token from YOUR backend.
const session = await fetch("/loyalty/session", { method: "POST" }).then(r => r.json());

// 2. Call Gibbond with the customer token.
async function loyalty(query, variables) {
  const res = await fetch("https://app.gibbond.com/api/v1/graphql", {
    method: "POST",
    headers: {
      "Authorization": "Bearer " + session.token,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query, variables }),
  });
  return (await res.json()).data;
}

// Show the member their points, tier, and reward codes.
const { me } = await loyalty("{ me { name balance lifetime tier { name multiplier } } }");
const { activeCodes } = await loyalty(
  "query($id: ID!) { activeCodes(customerId: $id) { code rewardName status expiresAt } }",
  { id: session.customerId },
);

renderRewardsScreen(me, activeCodes);

Échanger depuis l’app

Un jeton client permet d’échanger une récompense pour soi-même, sans clé marchand sur l’appareil. La mutation renvoie un code de réduction à usage unique à appliquer au paiement :

const { redeemReward } = await loyalty(
  "mutation($id: ID!, $reward: ID!) { redeemReward(customerId: $id, rewardId: $reward) { ok code { code rewardName pointsSpent expiresAt } } }",
  { id: session.customerId, reward: "gid_or_reward_id_from_rewards_query" },
);
if (redeemReward.ok) applyDiscountCode(redeemReward.code.code);
ℹ️

Par défaut, les jetons sont valables 15 minutes (définis ttlSeconds jusqu’à 24h). Si un appel renvoie UNAUTHENTICATED, appelle à nouveau /loyalty/session pour obtenir un nouveau jeton et réessaie.

Modèle 3 — Attribuer des points depuis un autre canal de vente

Lorsqu’une commande est passée ailleurs que sur ton checkout Shopify — sur un POS, une marketplace ou une boutique headless — signale-la avec awardPointsForOrder. Gibbond applique le multiplicateur de palier, les boosts actifs et les étapes comme pour une commande Shopify native. orderId rend les nouvelles tentatives sûres :

await gibbond(
  `mutation($input: OrderInput!) {
    awardPointsForOrder(input: $input) {
      ok
      pointsApplied
      balance { balance lifetime }
    }
  }`,
  {
    input: {
      orderId: "pos-2026-000123",
      customerId: "gid://shopify/Customer/123456789",
      customerEmail: "ada@example.com",
      orderName: "POS #000123",
      lineItems: [
        { productId: "gid://shopify/Product/55", collectionIds: ["gid://shopify/Collection/9"], lineTotal: 42.0 },
        { lineTotal: 8.5 },
      ],
    },
  },
  process.env.GIBBOND_API_KEY,
);

Chaque ligne contient un productId facultatif, des collectionIds facultatifs et un lineTotal obligatoire : les boosts et exclusions par produit ou collection s’appliquent comme pour les commandes Shopify. Pour confirmer les points plus tard, par exemple après l’exécution de la commande, appelle confirmPoints(customerId, orderId) ; pour les annuler à la suite d’un remboursement, appelle clawbackPoints avec une clé ADMIN.

Erreurs, nouvelles tentatives et idempotence

  • Codes. Les erreurs sont renvoyées dans un tableau GraphQL errors ; consulte errors[0].extensions.code : UNAUTHENTICATED (401), FORBIDDEN (403, autorisation incorrecte), RATE_LIMITED (429), BAD_USER_INPUT (400), NOT_FOUND (404).
  • Limites de requêtes. 600 requêtes par minute et par identifiant. En cas de 429, attends le nombre de secondes indiqué dans l’en-tête Retry-After, puis réessaie.
  • Idempotence. Transmets une idempotencyKey à awardPoints et un orderId stable à awardPointsForOrder. Renvoyer la même clé ou la même commande ne crédite pas les points une seconde fois : tu peux donc réessayer après une erreur réseau.
  • Expiration du jeton. Si tu reçois UNAUTHENTICATED avec un jeton client, crée-en un nouveau et réessaie.

Liste de vérification de sécurité

  • Conserve les clés API uniquement dans des secrets côté serveur : jamais dans les bundles d’app, le code navigateur ou le contrôle de version.
  • Utilise un jeton client pour tout ce qui s’exécute sur un appareil ; n’utilise jamais de clé API.
  • Accorde à chaque intégration l’autorisation minimale dont elle a besoin (une clé READ pour les tableaux de bord, WRITE pour gagner/échanger des points et ADMIN uniquement pour les remboursements/corrections).
  • Si une clé a pu être divulguée, renouvelle-la immédiatement depuis l’admin : sa révocation est instantanée.
  • Crée des jetons client avec la durée la plus courte possible pour ttlSeconds.

Pour obtenir la liste complète des types, requêtes, mutations et champs, consulte la référence de l’API.