Recettes

Enquêter sur un paiement échoué

Un outil de support en lecture seule : un identifiant de paiement en entrée, la cause de l'échec en sortie.

Créez un outil de support qui prend un identifiant de paiement et répond à la question "pourquoi ce paiement a-t-il échoué ?" à partir de votre API de paiements et de votre page de statut. L'agent ne peut que lire : il ne peut rien rembourser, réessayer ni modifier. La mise en place prend environ quinze minutes.

Une exécution de ce type dure généralement de 10 à 15 secondes. L'essentiel du coût est le calcul, facturé 0,0002 $ par seconde, et chaque page que l'agent récupère coûte 0,001 $. La part du modèle dépend du modèle que vous choisissez : lisez donc cost_micros sur vos premières exécutions. Le budget de 0,50 $ est un plafond, pas un prix.

  1. Rassembler ce qu'il vous faut

    • Un compte Ephemeral avec des crédits, et deux clés API depuis Clés API : une clé membre pour votre backend, qui lance les exécutions, et une clé administrateur pour la configuration unique de la connexion. Voir rôles et clés.
    • L'URL de base de votre API de paiements (HTTPS, accessible depuis Internet) et une clé en lecture seule pour celle-ci, par exemple une clé restreinte qui ne peut que lire les paiements.
    • Une page de statut publique sur un seul domaine, ici status.example.com.
    • Un point de terminaison HTTPS sur votre serveur, qui recevra le résultat.
    Terminal
    export EPHEMERAL_URL=https://app.ephemer.al
    export EPHEMERAL_API_KEY=eak_…          # member key, for your backend
    export EPHEMERAL_ADMIN_KEY=eak_…        # admin key, for setup only
    export EPHEMERAL_CALLBACK_SECRET=$(openssl rand -hex 16)   # 32 characters; keep it on your server
  2. Écrire et téléverser le playbook

    Le playbook contient le savoir-faire de votre équipe : quels appels effectuer et comment lire la réponse. Gardez-le court et concret. L'agent le reçoit sous forme de fichier en lecture seule, à côté de la tâche.

    playbook.md
    # Failed payment playbook
    
    You can only read. The payments connection allows GET, and web access is limited to status.example.com.
    
    1. GET /payments/{payment_id}. Note status, amount, currency, failure_code and failure_message.
    2. GET /payments/{payment_id}/attempts. Check whether earlier attempts failed for the same reason.
    3. If failure_code is processor_error or timeout, read https://status.example.com for incidents around that time.
    4. Decide the cause:
       - card_declined, insufficient_funds, expired_card: the customer's bank or card. Suggest a retry or another card.
       - processor_error, timeout: ours or the processor's. Say whether the status page shows an incident.
       - fraud_review: a risk rule held it. Do not suggest bypassing it.
    5. Never guess. If a call fails or a field is missing, say so.
    
    Answer in at most six lines: the cause, the evidence (field and value) and the next step for the support agent.
    Do not include card numbers or customer email addresses.
    curl $EPHEMERAL_URL/v1/files \
      -H "Authorization: Bearer $EPHEMERAL_API_KEY" \
      -F [email protected]

    Le fichier est enregistré sous le nom playbook.md. Le téléverser à nouveau ajoute une nouvelle version, et les workflows qui désignent ce fichier utilisent la plus récente au démarrage d'une exécution. Voir fichiers.

  3. Créer la connexion payments-api

    Une connexion conserve l'adresse et la clé de l'API à l'intérieur d'Ephemeral. L'agent l'appelle par son nom et ne voit jamais la clé. Définissez allowed_methods sur ["GET"] pour que toute autre méthode soit refusée.

    curl -X PUT $EPHEMERAL_URL/v1/connections/payments-api \
      -H "Authorization: Bearer $EPHEMERAL_ADMIN_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "type": "http",
        "url": "https://api.example.com/v1",
        "auth": { "type": "bearer", "token": "rk_live_…" },
        "allowed_methods": ["GET"],
        "instructions": "Look up a payment with GET /payments/{id} and its attempts with GET /payments/{id}/attempts. Amounts are in cents."
      }'

    Vous préférez le tableau de bord ? Ouvrez Connexions, choisissez Ajouter une connexion puis API HTTP, saisissez le nom payments-api et l'URL de base, choisissez Jeton Bearer et collez la clé, puis sélectionnez uniquement GET sous Méthodes HTTP autorisées. Les instructions indiquent à l'agent quels points de terminaison existent ; elles lui sont montrées, donc n'y mettez aucun secret.

    Relire la connexion n'affiche aucun jeton, et GET /v1/catalog/connections la liste aux membres avec son type et ses méthodes autorisées, sans l'URL ni aucun identifiant.

  4. Enregistrer le workflow

    Un workflow regroupe la tâche, le playbook, la connexion et les limites sous un seul nom, si bien que votre backend n'envoie que l'identifiant du paiement. L'agent voit la connexion sous le nom payments. L'accès web est limité à la page de statut, et le shell est désactivé car la tâche n'a besoin d'aucune commande.

    curl -X PUT $EPHEMERAL_URL/v1/workflows/investigate-payment \
      -H "Authorization: Bearer $EPHEMERAL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "prompt": "Find out why this payment failed.",
        "files": [{ "file": "playbook.md" }],
        "connections": { "payments": { "ref": "payments-api", "allowed_methods": ["GET"] } },
        "web": { "allow": ["status.example.com"] },
        "shell": false,
        "timeout_seconds": 120
      }'

    Aucun model n'est défini : chaque exécution utilise donc le modèle par défaut au moment où elle démarre ; choisissez-en un autre dans le catalogue de modèles si vous le souhaitez. L'identifiant du paiement, le budget et le callback appartiennent à chaque exécution, pas au workflow.

  5. Lancer une exécution depuis votre backend

    Envoyez le nom du workflow, l'identifiant du paiement, le budget et un callback. L'Idempotency-Key rend une nouvelle tentative sans risque : une clé répétée renvoie l'exécution déjà existante avec 200 au lieu d'en lancer une seconde. Utilisez une clé que vous pouvez reconstruire, comme l'identifiant du paiement ou celui de votre ticket.

    const paymentId = "pay_123";
    
    const response = await fetch(`${process.env.EPHEMERAL_URL}/v1/runs`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.EPHEMERAL_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `investigate-${paymentId}`,
      },
      body: JSON.stringify({
        workflow: "investigate-payment",
        input: { payment_id: paymentId },
        max_cost_micros: 500000,
        callback: {
          url: "https://example.com/hooks/ephemeral",
          secret: process.env.EPHEMERAL_CALLBACK_SECRET,
        },
      }),
    });
    const run = await response.json(); // { id: "run_…", status: "queued", … }

    La réponse est 202 Accepted avec l'objet d'exécution, au statut queued. L'exécution démarre dès qu'un worker est libre. L'URL du callback doit utiliser HTTPS et pointer vers une adresse publique, et un secret doit comporter de 24 à 1 024 caractères ; sinon la requête échoue avec 400 et rien n'est lancé. Sur le service hébergé, 402 signifie que vos crédits ne couvrent pas le budget.

  6. Vérifier le callback

    Lorsque l'exécution atteint un statut final, Ephemeral envoie un POST signé avec l'objet d'exécution. Vérifiez la signature sur les octets bruts du corps avant de faire confiance à quoi que ce soit. Le texte signé est l'en-tête X-Ephemeral-Timestamp, un point et le corps, signés en HMAC-SHA256 avec votre secret. Voir callbacks pour le schéma complet.

    Node.js, sans dépendance
    import crypto from "node:crypto";
    import http from "node:http";
    
    const handled = new Set(); // use your database in production
    
    http
      .createServer((req, res) => {
        const chunks = [];
        req.on("data", (chunk) => chunks.push(chunk));
        req.on("end", () => {
          const body = Buffer.concat(chunks);
          const timestamp = req.headers["x-ephemeral-timestamp"] ?? "";
          const signature = req.headers["x-ephemeral-signature"] ?? "";
          const expected =
            "sha256=" +
            crypto
              .createHmac("sha256", process.env.EPHEMERAL_CALLBACK_SECRET)
              .update(timestamp + ".")
              .update(body)
              .digest("hex");
    
          const valid =
            signature.length === expected.length &&
            crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
          const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
          if (!valid || !fresh) return res.writeHead(401).end();
    
          const run = JSON.parse(body);
          if (!handled.has(run.id)) {
            handled.add(run.id);
            // Attach run.output to the support ticket for run.id here.
          }
          res.writeHead(204).end(); // any 2xx counts as delivered
        });
      })
      .listen(3000); // serve this behind HTTPS at /hooks/ephemeral

    Toute réponse autre que 2xx est retentée avec des délais croissants : la même exécution peut donc arriver deux fois. C'est pourquoi le gestionnaire mémorise les ID d'exécution.

  7. Lire le résultat

    Le corps du callback est l'objet d'exécution. Pour le paiement d'exemple, il ressemble à ceci :

    JSON
    {
      "id": "run_118622d16f00105f7bba6d2281774705",
      "status": "succeeded",
      "output": "- **Cause:** The customer’s bank declined the payment for insufficient funds.\n- **Evidence:** `failure_code: insufficient_funds`; `failure_message: The card issuer declined the charge: insufficient funds.`\n- **Attempts:** Both attempts failed for the same reason.\n- **Next step:** Suggest the customer retry after resolving the balance issue or use another card.",
      "created_at": "2026-10-03T09:14:05.118Z",
      "started_at": "2026-10-03T09:14:05.402Z",
      "finished_at": "2026-10-03T09:14:16.447Z",
      "max_cost_micros": 500000,
      "cost_micros": 2618,
      "billing_pending": false,
      "zero_data_retention": false
    }

    output est la réponse de l'agent sous forme de texte. cost_micros est le coût total en millionièmes de dollar, ici 0,002618 $. Une exécution qui n'a pas réussi a le status failed, timed_out ou cancelled et une error au lieu d'un résultat ; montrez cela à votre agent plutôt que rien. Si billing_pending vaut true, le coût est encore provisoire : relisez l'exécution avec GET /v1/runs/{id} un peu plus tard. Pour voir chaque appel effectué par l'agent, ouvrez l'exécution dans le tableau de bord ou diffusez ses événements.

Pourquoi c'est sûr

RisqueCe qui l'empêche
L'agent modifie ou rembourse un paiement.La connexion et le workflow n'autorisent que GET ; toute autre méthode est refusée avec HTTP method not permitted.
La clé de paiements fuite.La clé reste dans Ephemeral. La microVM n'a ni réseau ni clé ; le broker ajoute la clé à chaque requête et retire les identifiants des réponses.
Des données partent vers un site inconnu.L'accès web n'autorise que status.example.com, et les redirections sont vérifiées par rapport à la même liste. Voir accès web.
L'agent exécute des commandes.shell is false.
Une exécution coûte plus que prévu.Le budget de 500 000 micros (0,50 $) est réservé au départ et ne peut pas être dépassé ; timeout_seconds met fin à l'exécution après 120 secondes.
Quelqu'un falsifie le résultat.Votre point de terminaison n'accepte que les requêtes dont la signature est valide.
Ne donnez à la clé en lecture seule qu'un accès en lecture
La connexion limite ce que l'agent peut appeler, mais une clé qui ne peut que lire est la garantie la plus solide. Créez chez votre fournisseur de paiements une clé restreinte pour cette seule connexion, afin de pouvoir la révoquer indépendamment.
Ajuster le playbook
Essayez-le sur quelques échecs connus et lisez le résultat. Lorsqu'il se trompe, modifiez le playbook et téléversez-le à nouveau ; l'exécution suivante utilise le nouveau fichier. Voir workflows pour la façon dont les champs enregistrés et les remplacements par exécution se combinent.
© 2026 EphemeralRéalisé par erni.works
esc

Aucun résultat pour « ».