Recetas

Investigar un pago fallido

Una herramienta de soporte de solo lectura: entra un ID de pago, sale la causa del fallo.

Crea una herramienta de soporte que recibe un ID de pago y responde "¿por qué falló este pago?" a partir de tu API de pagos y de tu página de estado. El agente solo puede leer: no puede reembolsar, reintentar ni cambiar nada. La configuración lleva unos quince minutos.

Una ejecución como esta suele durar de 10 a 15 segundos. La mayor parte del coste es el cómputo, que se factura a $0.0002 por segundo, y cada página que obtiene el agente cuesta $0.001. La parte del modelo depende del modelo que elijas, así que lee cost_micros en tus primeras ejecuciones. El presupuesto de $0.50 es un tope, no un precio.

  1. Reúne lo que necesitas

    • Una cuenta de Ephemeral con créditos y dos claves de API desde Claves de API: una clave de miembro para tu backend, que inicia las ejecuciones, y una clave de administrador para la configuración única de la conexión. Consulta roles y claves.
    • La URL base de tu API de pagos (HTTPS, accesible desde internet) y una clave de solo lectura para ella, como una clave restringida que solo pueda leer pagos.
    • Una página de estado pública en un solo dominio, aquí status.example.com.
    • Un endpoint HTTPS en tu servidor que recibirá el resultado.
    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. Escribe y sube el playbook

    El playbook es donde va el conocimiento de tu equipo: qué llamadas hacer y cómo interpretar la respuesta. Mantenlo corto y concreto. El agente lo recibe como un archivo de solo lectura junto con la tarea.

    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]

    El archivo se guarda con el nombre playbook.md. Si lo subes de nuevo se añade una versión nueva, y los flujos de trabajo que nombran el archivo toman la última cuando se inicia una ejecución. Consulta archivos.

  3. Crea la conexión payments-api

    Una conexión guarda dentro de Ephemeral la dirección y la clave de la API. El agente la llama por su nombre y nunca ve la clave. Establece allowed_methods en ["GET"] para que se rechace cualquier otro método.

    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."
      }'

    ¿Prefieres el panel? Abre Conexiones, elige Añadir conexión y API HTTP, escribe el nombre payments-api y la URL base, elige Token de portador y pega la clave, y selecciona solo GET en Métodos HTTP permitidos. Las instructions le indican al agente qué endpoints existen; se le muestran, así que no incluyas secretos.

    Al leer la conexión de nuevo no aparece el token, y GET /v1/catalog/connections la lista para los miembros con su tipo y sus métodos permitidos, sin la URL ni ninguna credencial.

  4. Guarda el flujo de trabajo

    Un flujo de trabajo reúne la tarea, el playbook, la conexión y los límites bajo un solo nombre, así que tu backend solo envía el ID del pago. El agente ve la conexión como payments. El acceso web se limita a la página de estado, y el shell está desactivado porque la tarea no necesita comandos.

    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
      }'

    No se define model, así que cada ejecución usa el modelo predeterminado en el momento en que se inicia; elige otro del catálogo de modelos si lo prefieres. El ID del pago, el presupuesto y el callback pertenecen a cada ejecución, no al flujo de trabajo.

  5. Inicia una ejecución desde tu backend

    Envía el nombre del flujo de trabajo, el ID del pago, el presupuesto y un callback. La Idempotency-Key hace seguro un reintento: una clave repetida devuelve la ejecución que ya existe con 200 en lugar de iniciar una segunda. Usa una clave que puedas reconstruir, como el ID del pago o el ID de tu 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 respuesta es 202 Accepted con el objeto de la ejecución, en estado queued. La ejecución empieza en cuanto hay un worker libre. La URL del callback debe usar HTTPS y resolverse a una dirección pública, y el secreto debe tener entre 24 y 1,024 caracteres; de lo contrario la solicitud falla con 400 y no se inicia nada. En el servicio alojado, 402 significa que tus créditos no cubren el presupuesto.

  6. Verifica el callback

    Cuando la ejecución llega a un estado final, Ephemeral envía un POST firmado con el objeto de la ejecución. Comprueba la firma contra los bytes sin procesar del cuerpo antes de fiarte de nada de lo que contiene. El texto firmado es la cabecera X-Ephemeral-Timestamp, un punto y el cuerpo, firmado con HMAC-SHA256 usando tu secreto. Consulta callbacks para ver el esquema completo.

    Node.js, no dependencies
    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

    Cualquier respuesta que no sea 2xx se reintenta con retrasos crecientes, así que la misma ejecución puede llegar dos veces. Por eso el manejador recuerda los ID de ejecución.

  7. Lee el resultado

    El cuerpo del callback es el objeto de la ejecución. Para el pago de ejemplo se ve así:

    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 es la respuesta del agente como texto. cost_micros es el coste total en millonésimas de dólar, aquí $0.002618. Una ejecución que no tuvo éxito tiene status failed, timed_out o cancelled y un error en lugar de una salida; muéstrale eso a tu agente de soporte en vez de nada. Si billing_pending es true, el coste aún es provisional: vuelve a leer la ejecución con GET /v1/runs/{id} un poco más tarde. Para ver cada llamada que hizo el agente, abre la ejecución en el panel o transmite sus eventos.

Por qué es seguro

RiesgoQué lo impide
El agente modifica o reembolsa un pago.La conexión y el flujo de trabajo permiten solo GET; cualquier otro método se rechaza con HTTP method not permitted.
Se filtra la clave de pagos.La clave permanece en Ephemeral. La microVM no tiene red ni clave; el intermediario añade la clave a cada solicitud y elimina las credenciales de las respuestas.
Los datos van a un sitio desconocido.El acceso web permite solo status.example.com, y las redirecciones se comprueban contra la misma lista. Consulta acceso web.
El agente ejecuta comandos.shell is false.
Una ejecución cuesta más de lo esperado.El presupuesto de 500,000 micros ($0.50) se reserva al inicio y no puede superarse; timeout_seconds termina la ejecución a los 120 segundos.
Alguien falsifica el resultado.Tu endpoint solo acepta solicitudes cuya firma se verifica.
Da a la clave de solo lectura únicamente acceso de lectura
La conexión limita lo que el agente puede llamar, pero una clave que solo puede leer es la garantía más fuerte. Crea una clave restringida en tu proveedor de pagos solo para esta conexión, de modo que puedas revocarla por separado.
Afinar el playbook
Pruébalo con unos cuantos fallos conocidos y lee la salida. Cuando sea incorrecta, cambia el playbook y súbelo de nuevo; la siguiente ejecución usa el archivo nuevo. Consulta flujos de trabajo para ver cómo se combinan los campos guardados y los valores de cada ejecución.
© 2026 EphemeralProducido por erni.works
esc

Nada coincide con “”.