# Backend externe : servir une app depuis votre serveur

> Héberger la logique de votre app chez vous : manifeste, secret et signature des requêtes, createBackend, ce qui reste réservé au code hébergé, paiements, tests, review.
> Catégorie : Référence · mis à jour le 2026-09-04 · plateforme v1

## Deux façons de faire tourner une app

- **Code hébergé** (`main`) — votre `defineApp` tourne chez Capibara, dans
  un bac à sable (workerd sous gVisor, réseau limité aux hôtes déclarés).
  C'est la voie par défaut : rien à héberger, données et moteurs à portée de
  `ctx`.
- **Backend externe** (`backend: { url }`) — le MÊME `defineApp`, mais
  c'est **votre serveur** qui reçoit les requêtes du protocole (écrans,
  actions, événements, planifications, HTTP entrant), signées et horodatées,
  et qui répond du Kit. Capibara rend l'interface, applique les mêmes bornes
  et le même disjoncteur.

Choisissez le backend externe quand vous avez déjà un serveur, une base ou
des dépendances lourdes. Les contreparties sont explicites : l'organisation
voit **« Backend chez l'éditeur (votre hôte) »** et consent à l'envoi de ses
interactions vers votre serveur, la **review humaine est obligatoire**, la
latence réseau s'ajoute à chaque écran, et la disponibilité comme la sécurité
de ce serveur vous incombent ([CGV marketplace](/legal/cgv-marketplace) §11 :
incident notifié sous 48 h, données effacées 30 jours après désinstallation).

---

## 1. Déclarer le backend au manifeste

```json
{
  "key": "mon-app",
  "name": "Mon app",
  "version": "1.0.0",
  "backend": { "url": "https://api.mon-app.example/capibara" },
  "returnHosts": ["mon-app.example"],
  "contributes": { "menuEntries": [{ "key": "home", "label": "Mon app", "view": "home" }] }
}
```

- `backend.url` : **HTTPS**, hôte **public** (jamais une IP, ni localhost, ni
  un domaine interne). **Exclusif de `main`** : une app est hébergée OU
  externe, jamais les deux.
- Ce qu'un backend peut servir : entrées de menu `view`, widgets
  `kind: "kit"`, panneaux, blocs et pages du site public, `events`,
  `schedules`, `http`.
- Ce qui reste **réservé au code hébergé** : `collections`, `files.private`
  et les capabilities des [moteurs](/dev/docs/moteurs) (`mail.send`,
  `notify.send`, `calendar.project`, `search.index`,
  `approvals.request`). Le manifeste les refuse sans `main` et renvoie vers
  les [façades](/dev/docs/facades).
- `returnHosts` (≤ 5) : les domaines vers lesquels vos liens de paiement
  peuvent ramener le client (§5).

Référence complète des champs : [Le manifeste](/dev/docs/manifeste).

---

## 2. Le secret et la signature

Le secret `cbk_…` se génère dans le portail, sur la page de l'app,
section **Backend & webhooks › « Backend externe »**. Il est affiché **une
fois**, chiffré au repos, et se régénère à volonté (l'ancien est invalidé
aussitôt). Il vit dans les variables d'environnement de votre serveur, jamais
dans le dépôt. **Tant qu'aucun secret n'est posé, Capibara n'envoie rien**
(journal de l'app : `backend_secret_missing`).

Chaque requête arrive ainsi :

```http
POST /capibara HTTP/1.1
content-type: application/json
x-capibara-timestamp: 1756900000
x-capibara-signature: t=1756900000,v1=<hex(hmac-sha256(secret, "1756900000." + corps))>
x-capibara-kind: render
x-capibara-delivery: <identifiant de l'invocation = ligne du journal>
```

Vérification : recalculez `hmac-sha256(secret, t + "." + corps BRUT)`,
comparez en temps constant, refusez si le `t` s'écarte de plus de
**300 s** de votre horloge (`stale`). `x-capibara-delivery` identifie
l'invocation : servez-vous-en pour rendre vos traitements idempotents. Avec
le SDK, tout cela est fait par `createBackend` (§4).

---

## 3. Le protocole

Le corps JSON est la même enveloppe que celle reçue par le code hébergé
([Le runtime › Le protocole d'interaction](/dev/docs/runtime)) : `kind`
(`render`, `action`, `event`, `schedule`, `http`), la surface, l'installation
(identifiant, configuration, réglages déchiffrés), `viewer` ou `visitor`,
le sujet, l'état signé, les paramètres ou le formulaire. Vous répondez
`{ ok, result, logs }` — `result` est un document du [Kit](/dev/docs/kit)
pour un rendu, le résultat d'action (`ui`, `patch`, `toast`, `navigate`,
`openForm`, `close`, `refresh`) sinon.

Bornes, identiques au code hébergé : **15 s** par requête, réponse ≤ **1 Mo**,
corps entrant ≤ 256 Ko, journal ≤ 200 lignes de 500 caractères, **8
invocations simultanées** par app, disjoncteur (10 échecs sur 5 minutes à
≥ 50 % d'erreurs → app suspendue 5 minutes, puis une sonde). Une réponse hors
enveloppe est une erreur pour l'utilisateur, jamais un rendu partiel.

---

## 4. Avec le SDK : `createBackend`

```ts
import { createBackend, defineApp, ui, CapibaraClient } from '@capibara-dev/sdk';

const app = defineApp({
  pages: { home: () => ui.text({ markdown: 'Rendu depuis mon serveur.' }) },
});

export const backend = createBackend({
  app,
  secret: process.env.CAPIBARA_BACKEND_SECRET!,                              // cbk_…, jamais dans le dépôt
  capibara: new CapibaraClient({ token: process.env.CAPIBARA_API_KEY! }),  // ctx.capibara.* via l'API publique
});

// Node / Express : const { status, body } = await backend.handle(rawBody, req.headers);
// Next.js, Cloudflare Workers, Deno, Bun : export default { fetch: (req) => backend.fetch(req) };
```

- `handle(rawBody, headers)` pour les serveurs à corps brut, `fetch(request)`
  pour les runtimes à `Request`, `dispatch(req)` pour vos tests.
- `maxSkewSec` (défaut 300) et `onLog` sont optionnels ;
  `verifyBackendSignature(rawBody, header, secret)` et
  `parseBackendSignature` si vous branchez vous-même.
- Le `CapibaraClient` porte une clé API `ck_live_…` (portail › Clés & API)
  ou un jeton OAuth : `createBackend` en dérive `ctx.capibara` pour
  l'installation appelante (en-tête `X-Capibara-Install`, compte de service
  de l'app, scopes consentis par l'organisation).

Détail des exports : [SDK › Backend externe](/dev/docs/sdk).

---

## 5. Ce qui change dans `ctx`

| Membre | Code hébergé | Backend externe |
|---|---|---|
| `ctx.capibara.*` (façades) | pont interne | [API publique](/dev/docs/api-publique) via le `CapibaraClient` fourni |
| `ctx.fetch` | réseau limité aux hôtes `http.fetch:<hôte>` du manifeste | le `fetch` de votre serveur, sans restriction |
| `ctx.kv`, `ctx.data`, `ctx.files` | disponibles | rejettent avec une explication : vos données vivent chez vous |
| `ctx.mail`, `ctx.notify`, `ctx.calendar`, `ctx.search`, `ctx.approvals` | disponibles | rejettent ; l'écriture métier passe par les façades |
| `ctx.followup` | disponible | rejette |
| `ctx.log` | journal de l'invocation | identique, renvoyé dans `logs` |
| `ctx.install.config` | réglages déchiffrés | identiques (transmis dans la requête signée) |

**Paiements et retour sur votre site.** Les façades
`billing.documents.payLink` et `billing.charges.create` acceptent un
`returnUrl` : il est validé contre `returnHosts` puis signé ; le client règle
sur le site de l'organisation et voit un bouton « Retourner sur votre-hôte ».
Les événements `sales.document.paid` et `pos.charge.paid` vous préviennent
(`events` du manifeste, ou [webhooks](/dev/docs/webhooks)). Cf. [Moteurs ›
Paiements](/dev/docs/moteurs).

---

## 6. Tester et déployer

- `capibara init --template external` livre le starter « Intégration
  externe » : manifeste `backend` + `returnHosts`, une page et un widget Kit,
  `server/backend.ts` sur `createBackend`.
- Le harnais `@capibara-dev/sdk/testing` fonctionne tel quel (`defineApp`
  est le même) ; `backend.dispatch(req)` rejoue une requête du protocole.
- Une installation de test (`capibara dev --org <slug>` ou section Tests du
  portail) appelle **votre URL** : elle doit être joignable en HTTPS public —
  Capibara ne fournit pas de tunnel, prévoyez une adresse de préproduction.
- `capibara logs` et la section Journal montrent chaque invocation (latence,
  erreur, vos `logs`).

---

## 7. Publication, consentement, review

Le badge **« Backend chez l'éditeur (hôte) »** et la phrase de consentement
sont dérivés du manifeste et affichés partout : catalogue des apps, écran
d'installation, fiche marketplace, panneau de review. Une version à backend
externe n'est **jamais auto-approuvée** : un humain la relit
([Publication](/dev/docs/publication)).

---

## Backend externe ≠ webhooks

Les [webhooks sortants](/dev/docs/webhooks) (`createServerHandler`) vous
**notifient** des événements d'une organisation — pour toute app, hébergée ou
non. Le backend externe **sert** l'app (écrans, actions, planifications).
Les deux se combinent : une app hébergée peut recevoir des webhooks, un
backend externe peut aussi s'abonner aux événements.
