Crie uma ferramenta de suporte que recebe o ID de um pagamento e responde "por que este pagamento falhou?" a partir da sua API de pagamentos e da sua página de status. O agente só pode ler: ele não consegue estornar, tentar de novo nem alterar nada. A configuração leva cerca de quinze minutos.
Uma execução como esta costuma levar de 10 a 15 segundos. A maior parte do custo é a computação, cobrada a $0.0002 por segundo, e cada página que o agente busca custa $0.001. A parte do modelo depende do modelo que você escolher; por isso, leia cost_micros nas suas primeiras execuções. O orçamento de $0.50 é um teto, não um preço.
Reúna o que você precisa
- Uma conta do Ephemeral com créditos e duas chaves de API em Chaves de API: uma chave de membro para o seu backend, que inicia as execuções, e uma chave de administrador para a configuração única da conexão. Veja funções e chaves.
- A URL base da sua API de pagamentos (HTTPS, acessível pela internet) e uma chave somente leitura para ela, como uma chave restrita que só pode ler pagamentos.
- Uma página de status pública em um único domínio, aqui
status.example.com. - Um endpoint HTTPS no seu servidor que receberá o resultado.
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 serverEscreva e envie o playbook
O playbook é onde fica o conhecimento da sua equipe: quais chamadas fazer e como ler a resposta. Mantenha-o curto e concreto. O agente o recebe como um arquivo somente leitura junto com a tarefa.
# 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]O arquivo é armazenado com o nome
playbook.md. Enviá-lo de novo adiciona uma nova versão, e os fluxos de trabalho que citam o arquivo usam a versão mais recente quando uma execução começa. Veja arquivos.Crie a conexão payments-api
Uma conexão guarda o endereço e a chave da API dentro do Ephemeral. O agente a chama pelo nome e nunca vê a chave. Defina
allowed_methodscomo["GET"]para que todos os outros métodos sejam recusados.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." }'Prefere o dashboard? Abra Conexões, escolha Adicionar conexão e API HTTP, informe o nome
payments-apie a URL base, selecione Token Bearer e cole a chave; depois marque apenasGETem Métodos HTTP permitidos. Asinstructionsdizem ao agente quais endpoints existem; ele as enxerga, então mantenha segredos fora delas.Ao ler a conexão de volta, nenhum token aparece, e
GET /v1/catalog/connectionsa lista para os membros com o tipo e os métodos permitidos, sem a URL nem qualquer credencial.Salve o fluxo de trabalho
Um fluxo de trabalho reúne a tarefa, o playbook, a conexão e os limites sob um único nome, e assim o seu backend só envia o ID do pagamento. O agente enxerga a conexão como
payments. O acesso à web fica limitado à página de status, e o shell fica desligado porque a tarefa não precisa de 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 }'Nenhum
modelé definido, então cada execução usa o modelo padrão no momento em que começa; escolha outro no catálogo de modelos se preferir. O ID do pagamento, o orçamento e o callback pertencem a cada execução, não ao fluxo de trabalho.Inicie uma execução a partir do seu backend
Envie o nome do fluxo de trabalho, o ID do pagamento, o orçamento e um callback. O
Idempotency-Keytorna uma nova tentativa segura: uma chave repetida retorna a execução que já existe com200em vez de iniciar uma segunda. Use uma chave que você consiga reconstruir, como o ID do pagamento ou o ID do seu 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", … }A resposta é
202 Acceptedcom o objeto da execução, no statusqueued. A execução começa assim que um worker estiver livre. A URL do callback deve usar HTTPS e resolver para um endereço público, e o segredo deve ter de 24 a 1.024 caracteres; caso contrário, a requisição falha com400e nada é iniciado. No serviço hospedado,402significa que os seus créditos não cobrem o orçamento.Verifique o callback
Quando a execução chega a um status final, o Ephemeral envia um único
POSTassinado com o objeto da execução. Confira a assinatura contra os bytes brutos do corpo antes de confiar em qualquer coisa nele. O texto assinado é o cabeçalhoX-Ephemeral-Timestamp, um ponto e o corpo, assinados com HMAC-SHA256 usando o seu segredo. Veja callbacks para o esquema completo.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/ephemeralQualquer resposta que não seja
2xxé tentada de novo com intervalos crescentes, então a mesma execução pode chegar duas vezes. É por isso que o handler memoriza os IDs das execuções.Leia o resultado
O corpo do callback é o objeto da execução. Para o pagamento de exemplo, ele fica assim:
{ "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é a resposta do agente em texto.cost_microsé o custo total em milionésimos de dólar, aqui $0.002618. Uma execução que não teve sucesso temstatusfailed,timed_outoucancellede umerrorno lugar de uma saída; mostre isso ao seu atendente em vez de nada. Sebilling_pendingfortrue, o custo ainda é provisório: leia a execução de novo comGET /v1/runs/{id}um pouco depois. Para ver cada chamada que o agente fez, abra a execução no dashboard ou acompanhe os seus eventos.
Por que é seguro
| Risco | O que impede |
|---|---|
| O agente altera ou estorna um pagamento. | A conexão e o fluxo de trabalho permitem apenas GET; qualquer outro método é recusado com HTTP method not permitted. |
| A chave de pagamentos vaza. | A chave fica no Ephemeral. A microVM não tem rede nem chave; o broker adiciona a chave a cada requisição e remove as credenciais das respostas. |
| Os dados vão para um site desconhecido. | O acesso à web permite apenas status.example.com, e os redirecionamentos são conferidos contra a mesma lista. Veja acesso à web. |
| O agente executa comandos. | shell é false. |
| Uma execução custa mais do que o esperado. | O orçamento de 500.000 micros ($0.50) é reservado no início e não pode ser excedido; timeout_seconds encerra a execução após 120 segundos. |
| Alguém forja o resultado. | O seu endpoint aceita apenas requisições cuja assinatura é válida. |