레시피

실패한 결제 조사

읽기 전용 지원 도구: 결제 ID를 넣으면 실패 원인이 나옵니다.

결제 ID를 받아 결제 API와 상태 페이지를 바탕으로 "이 결제는 왜 실패했는가?"에 답하는 지원 도구를 만듭니다. 에이전트는 읽기만 할 수 있어서 환불, 재시도, 변경은 할 수 없습니다. 설정에는 약 15분이 걸립니다.

이런 실행은 보통 10~15초가 걸립니다. 비용의 대부분은 초당 $0.0002로 청구되는 컴퓨팅이고, 에이전트가 가져오는 페이지마다 $0.001이 듭니다. 모델 사용량은 해당 모델의 자체 가격으로 청구되며 선택한 모델에 따라 달라지므로, 처음 몇 번의 실행에서 cost_micros를 확인하세요. 예산 $0.50는 상한이지 가격이 아닙니다.

  1. 필요한 것 준비

    • 크레딧이 있는 Ephemeral 계정과 API 키에서 만든 API 키 두 개입니다. 실행을 시작하는 백엔드용 member 키와, 연결을 한 번 설정할 때 쓰는 admin 키입니다. 역할과 키를 참고하세요.
    • 결제 API의 기본 URL(HTTPS, 인터넷에서 접근 가능)과 그 API의 읽기 전용 키입니다. 결제를 읽기만 할 수 있는 제한된 키가 좋습니다.
    • 하나의 도메인에 있는 공개 상태 페이지입니다. 여기서는 status.example.com입니다.
    • 결과를 받을 서버의 HTTPS 엔드포인트입니다.
    터미널
    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. 플레이북 작성과 업로드

    플레이북에는 팀의 지식을 담습니다. 어떤 호출을 하고 응답을 어떻게 해석할지입니다. 짧고 구체적으로 작성하세요. 에이전트는 이 파일을 작업과 함께 읽기 전용 파일로 받습니다.

    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]

    파일은 playbook.md라는 이름으로 저장됩니다. 다시 업로드하면 새 버전이 추가되고, 이 파일을 이름으로 지정한 워크플로는 실행이 시작될 때 최신 버전을 사용합니다. 파일을 참고하세요.

  3. payments-api 연결 만들기

    연결은 API의 주소와 키를 Ephemeral 안에 보관합니다. 에이전트는 이름으로 호출하며 키를 볼 수 없습니다. allowed_methods를 ["GET"]로 설정하면 그 밖의 모든 메서드는 거부됩니다.

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

    대시보드를 선호하시나요? 연결을 열고 연결 추가와 HTTP API를 선택한 다음, 이름 payments-api와 기본 URL을 입력하고 Bearer 토큰을 고른 뒤 키를 붙여 넣으세요. 그리고 허용된 HTTP 메서드에서 GET만 선택합니다. instructions는 에이전트에게 어떤 엔드포인트가 있는지 알려 주며 에이전트에게 그대로 보이므로, 비밀 값은 넣지 마세요.

    연결을 다시 조회해도 토큰은 표시되지 않으며, GET /v1/catalog/connections는 멤버에게 연결의 유형과 허용된 메서드만 보여 주고 URL이나 자격 증명은 보여 주지 않습니다.

  4. 워크플로 저장

    워크플로는 작업, 플레이북, 연결, 제한을 하나의 이름으로 묶어 두므로 백엔드는 결제 ID만 보내면 됩니다. 에이전트에게는 연결이 payments로 보입니다. 웹 액세스는 상태 페이지로 제한되고, 작업에 명령이 필요 없으므로 셸은 꺼 둡니다.

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

    model을 지정하지 않았으므로 각 실행은 시작 시점의 기본 모델을 사용합니다. 다른 모델을 쓰려면 모델 카탈로그에서 고르세요. 결제 ID, 예산, 콜백은 워크플로가 아니라 각 실행에 속합니다.

  5. 백엔드에서 실행 시작

    워크플로 이름, 결제 ID, 예산, 콜백을 보냅니다. Idempotency-Key 덕분에 재시도해도 안전합니다. 같은 키를 다시 보내면 두 번째 실행을 시작하지 않고 이미 있는 실행을 200으로 반환합니다. 결제 ID나 티켓 ID처럼 다시 만들 수 있는 키를 사용하세요.

    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", … }

    응답은 202 Accepted이며, 상태가 queued인 실행 객체가 담깁니다. 실행은 워커가 비는 즉시 시작됩니다. 콜백 URL은 HTTPS를 사용하고 공개 주소로 해석되어야 하며, 시크릿은 24~1,024자여야 합니다. 그렇지 않으면 요청은 400으로 실패하고 아무것도 시작되지 않습니다. 호스팅 서비스에서 402는 크레딧이 예산을 충당하지 못한다는 뜻입니다.

  6. 콜백 검증

    실행이 최종 상태에 도달하면 Ephemeral은 실행 객체를 담은 서명된 POST를 한 번 보냅니다. 내용을 신뢰하기 전에 본문의 원시 바이트를 기준으로 서명을 확인하세요. 서명 대상 텍스트는 X-Ephemeral-Timestamp 헤더, 마침표, 본문을 이어 붙인 것이며, 시크릿으로 HMAC-SHA256 서명합니다. 전체 방식은 콜백을 참고하세요.

    Node.js, 의존성 없음
    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

    2xx가 아닌 응답은 간격을 늘리며 재시도되므로 같은 실행이 두 번 도착할 수 있습니다. 핸들러가 실행 ID를 기억하는 이유입니다.

  7. 결과 읽기

    콜백의 본문은 실행 객체입니다. 예시 결제의 경우 다음과 같습니다.

    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은 에이전트의 답변 텍스트입니다. cost_micros는 전체 비용을 100만분의 1달러 단위로 나타낸 값이며, 여기서는 $0.002618입니다. 성공하지 못한 실행은 status가 failed, timed_out 또는 cancelled이고 output 대신 error가 있습니다. 상담원에게 아무것도 보여 주지 않는 대신 이를 보여 주세요. billing_pending이 true이면 비용은 아직 잠정 값이므로, 잠시 후 GET /v1/runs/{id}로 실행을 다시 조회하세요. 에이전트가 수행한 각 호출을 보려면 대시보드에서 실행을 열거나 이벤트를 스트리밍하세요.

안전한 이유

위험막는 방법
에이전트가 결제를 변경하거나 환불합니다.연결과 워크플로 모두 GET만 허용하며, 그 밖의 메서드는 HTTP method not permitted로 거부됩니다.
결제 키가 유출됩니다.키는 Ephemeral 안에 머뭅니다. microVM에는 네트워크도 키도 없으며, 브로커가 요청마다 키를 추가하고 응답에서 자격 증명을 제거합니다.
데이터가 알 수 없는 사이트로 나갑니다.웹 액세스는 status.example.com만 허용하며, 리디렉션도 같은 목록과 대조해 확인합니다. 웹 액세스를 참고하세요.
에이전트가 명령을 실행합니다.shell is false.
실행 비용이 예상보다 많이 나옵니다.예산 500,000 마이크로($0.50)는 시작할 때 예약되며 초과할 수 없습니다. timeout_seconds는 120초 뒤에 실행을 종료합니다.
누군가 결과를 위조합니다.엔드포인트는 서명이 검증되는 요청만 받습니다.
읽기 전용 키에는 읽기 권한만 부여하세요
연결은 에이전트가 호출할 수 있는 범위를 제한하지만, 읽기만 가능한 키가 더 확실한 보장입니다. 결제 제공업체에서 이 연결 전용으로 제한된 키를 만들어 두면 그 키만 따로 폐기할 수 있습니다.
플레이북 다듬기
이미 알고 있는 실패 사례 몇 건으로 실행해 보고 output을 확인하세요. 틀렸다면 플레이북을 고쳐 다시 업로드하세요. 다음 실행부터 새 파일을 사용합니다. 저장된 필드와 실행별 재정의가 어떻게 합쳐지는지는 워크플로를 참고하세요.
© 2026 Ephemeral제작: erni.works
esc

“”와(과) 일치하는 항목이 없습니다.