76%의 사망률은 시장의 문제가 아니라 나의 버그였다 — 그리고 나는 똑같은 실수를 네 번 더 저질렀다
요약
에이전트 결제 경제(x402)를 모니터링하던 중, 파서 오류로 인해 엔드포인트의 사망률을 76%로 잘못 측정했던 사례를 다룹니다. HTTP 응답 헤더(v2)를 무시하고 바디(v1)만 파싱하여 발생한 오류를 수정하는 과정과 기술적 교훈을 공유합니다.
핵심 포인트
- v2 프로토콜은 결제 정보를 응답 헤더에 포함함
- 바디만 파싱할 경우 정상적인 v2 서비스를 오류로 오판함
- 데이터 측정 도구 자체의 버그가 잘못된 시장 통계를 생성할 수 있음
- HTTP 헤더와 바디를 모두 고려하는 견고한 파서 설계의 중요성
2주 전, 나는 상장된 엔드포인트(endpoint)의 76%가 작동하지 않거나 유효하지 않다는 헤드라인과 함께 x402 에이전트 결제 경제(agent-payment economy)에 대한 감사 보고서를 발표했습니다.
그 수치는 틀렸습니다. 그것은 제 파서(parser) 자체를 측정하고 있었던 것이었습니다.
실제 수치는 **38%**입니다. 생태계가 회복되었기 때문이 아니라, 제 프로버(prober)가 HTTP 응답의 잘못된 절반을 읽고 있었기 때문입니다. 이 포스트는 그에 대한 수정 사항, 메커니즘, 그리고 첫 번째 실수를 수정하는 과정에서 동일한 계열로 저지른 네 가지 추가적인 실수에 관한 것입니다. 마지막 부분이 가장 유용한 정보가 될 것입니다.
여기 있는 모든 내용은 curl로 재현 가능합니다. 명령어는 본문에 포함되어 있습니다.
실수
x402는 402 Payment Required 응답에 결제 요구 사항을 포함합니다. v1에서는 JSON 바디(body)에 포함되어 있었습니다:
{ "x402Version": 1, "accepts": [{ "scheme": "exact", "maxAmountRequired": "10000", ... }] }
v2에서는 이것이 PAYMENT-REQUIRED 응답 헤더(base64 인코딩된 JSON)로 이동했으며, 바디(body)는 서버 구현 세부 사항이 되었습니다. 많은 v2 서비스는 accepts가 전혀 없는 바디를 반환하며, 때로는 단순히 {}만 반환하기도 합니다.
제 프로버(prober)는 바디(body)만 파싱했습니다. 그래서 살아있는 v2 서비스는 다음과 같이 보였습니다: HTTP 402 수신, accepts를 찾을 수 없음, 판정 malformed(형식 오류), 공개 권고 "결제하지 마세요: 작동하지 않거나 유효하지 않음."
그중 하나는 다음과 같습니다:
curl -sD - -o /dev/null https://api.onesource.io/api/chain/block-number \
| grep -i '^payment-required' | sed 's/^[^:]*: //' | base64 -d
완전하고 유효한 챌린지(challenge): scheme: exact, 네트워크 eip155:8453, amount: 1000 (0.001 USDC), 실제 payTo. 서비스는 정상 작동합니다. 항상 작동해 왔습니다. 하지만 제 오라클(oracle)은 에이전트들에게 신뢰 점수 100점 만점에 8점, 가동 시간(uptime) 0%라며 해당 서비스가 죽었다고 알려주었습니다.
**166개의 운영자(operator)에 걸친 1,869개의 엔드포인트(endpoint)**가 그런 상태였습니다. 그중에는 pro-api.coingecko.com도 포함되어 있었습니다.
또한 저는 카탈로그에 사과해야 했습니다. 저는 402index가 건강 상태를 과대평가하고 있다는 것을 게시해 왔는데, 실제로 '건강함(healthy)'으로 목록화된 것 중 약 30%만이 그러했습니다. 수정 후에는 동일한 비교 결과 ~68% (실시간 수치는 catalogAudit.accuracyPct에서 확인 가능하며 /status.json — 크롤링할 때마다 변동됨)가 나왔습니다. 카탈로그는 제가 말했던 것보다 훨씬 더 정직했습니다. 저의 것이 신뢰할 수 없는 측정값이었습니다.
수정 과정과 그 비용
파서를 고치는 것은 10줄에 불과했습니다. 본문(body)을 읽고, 헤더(header)를 읽은 다음, 헤더를 우선시하는 방식(즉, v2 클라이언트가 지불하는 것)으로 변경하고, 어떤 것이 이 문제를 가지고 있었는지 기록하는 것입니다.
비용이 많이 든 부분은 히스토리였습니다. 저의 가동 시간 측정값(uptime metric)은 healthy 판정을 받은 과거 관측치들의 비율인데, 이 서비스들 모두의 과거 관측치는 malformed으로 되어 있었습니다. 이것은 그들의 다운타임이 아니라, 그들의 기록에 반영된 저의 버그였습니다. 저는 1,869개 엔드포인트 중 유효한 헤더 챌린지를 반환하는 곳에서 3,920개의 malformed 히스토리 항목을 제거하고, 모든 unreachable 및 no-402 항목(저의 버그로는 생성할 수 없었던 것들)은 유지했으며, 수정된 각 기록에 historyRepairedAt, historyRepairedEntries와 함께 메모를 달았습니다. 이 마커들은 무료 /verify 응답에서 확인할 수 있어, 기록을 읽는 누구나 그것이 편집되었고 그 이유가 무엇인지 알 수 있습니다.
이는 새로운 문제를 야기했습니다: 관측치가 하나이고
curl -s https://pulsefeed.dev/status.json | jq '.ecosystem'
그리고 나는 정확히 똑같은 종류의 실수를 네 번 더 저질렀다
이 부분이 바로 내가 읽고 싶었던 대목이다.
스펙 (spec)보다 더 적게 알고 있는 파서 (parser)에 의해 데인 직후, 나는 유효한 402를 반환하지만 여전히 결제 가능한 제품이 아닌 엔드포인트 (endpoints)를 감지하는 탐지기를 추가했다. 몇 시간 지나지 않아, 그중 세 개가 동일한 방식으로, 동일한 이유로 틀렸다: 내 코드가 프로토콜 (protocol)을 인코딩한 것이 아니라, 프로토콜에 대한 나의 가정을 인코딩해 버린 것이다.
1. 나는 표준 스킴 (scheme)을 결제 불가능하다고 불렀다
나는 정산 가능한 유일한 스킴 (scheme)은 exact뿐이라고 결정했고, 그 외의 모든 것을 "표준 클라이언트로는 이를 정산할 수 없음 — 자금이 에스크로 (escrow)에서 돌이킬 수 없이 빠져나감"이라고 표시하여 높은 심각도로 게시했다.
x402 스펙 (spec)에는 네 가지 스킴 (scheme)이 있다:
curl -s https://api.github.com/repos/x402-foundation/x402/contents/specs/schemes \
| jq -r '.[] | select(.type=="dir") | .name'
# auth-capture, batch-settlement, exact, upto
내가 표시한 서비스인 api.bitrefill.com/x402/invoice/pay는 upto를 사용한다. 이는 스펙 (spec)에 정의된 정상적인 스킴 (scheme)이며, 온체인 (on-chain) 거래량 측면에서 생태계 (ecosystem) 내에서 가장 큰 수취처 중 하나다. 내 경고(alert)에 포함된 세 가지 별개의 주장은 거짓이었다. 나는 다른 프로토콜 (protocol) 필드에 대해 나 자신의 버그를 재현한 셈이었다.
진정한 구분은 결제 가능 여부가 아니다:
- 스펙 (spec) 외부 → 정산이 중개자 (facilitator)를 거치지 않으며, 자금이 에스크로 (escrow)에 있지 않음 — 표시할 가치가 있음;
- 스펙 (spec) 내에 있지만
exact가 아님 → 기본x402-fetch클라이언트는 추가 작업 없이 이를 결제할 수 없음. 이것은 호환성 참고 사항이지, 비난이 아니다.
내 인덱스 (index) 중 한 엔드포인트 (endpoint)는 진정으로 스펙 (spec) 밖에 있다: api.dynsuplabs.com은 exact-prepay-proof를 광고하며, 자체 extra.doc 필드는 클라이언트에게 중개자 (facilitator) 없이 직접 USDC 전송을 브로드캐스트 (broadcast)하도록 지시하며, 결제는 최종적이며 환불 불가능하다고 명시한다. 그 하나는 표시할 가치가 있었다. 나머지는 그렇지 않았다.
2. 나는 권한 한도 (authorization ceiling)를 가격으로 읽었다
동일한 서비스, 두 번째 오탐 (false alarm). 해당 챌린지는 amount: 1000000000 — 1,000 USDC를 포함하고 있습니다. 나의 가격 탐지기 (price detector)는 1,000달러짜리 API 호출을 보고 "터무니없는 가격"이라며 플래그를 표시했습니다.
upto에서 amount는 **서비스가 인출하도록 권한을 부여받은 최대 금액 (maximum the service is authorized to draw)**이지, 호출당 가격이 아닙니다. 이것은 기프트 카드 구매 흐름과 같으며, 실제 비용은 사용량에 따라 부과됩니다. 한도 (ceiling)를 다른 서비스의 호출당 가격과 비교하는 것은 서로 다른 두 수량을 비교하면서 그 차이를 사기라고 부르는 것과 같습니다.
이제 한도는 명시적인 노트가 포함된 별도의 필드에 존재하며, 가격 필드에 절대 들어가지 않고 가격 비교에도 사용되지 않습니다.
3. 나는 테스트해 보지도 않은 메커니즘을 설명했다
나열된 수백 개의 URL은 문서용 플레이스홀더 (documentation placeholders) — /api/chain/tx/:hash, /v1/{id}, /example — 로, 여전히 유효한 402 응답을 반환합니다. 나는 402 응답이 "라우트가 결정되기 전 미들웨어 (middleware)에 의해 제공"되며, 유료 에이전트는 "404를 받는다"라고 작성했습니다.
둘 다 추측이었습니다. 동일한 호스트에서 실제로 존재하지 않는 경로를 요청해 보았습니다:
curl -s -o /dev/null -w '%{http_code}\n' https://skills.onesource.io/api/chain/zzz-not-real-route # 308
curl -s -o /dev/null -w '%{http_code}\n' https://api.gocreativeai.com/definitely-not-a-real-zzz999 # 502
402가 아니었습니다. 즉, 라우트가 일치했던 것입니다 — 플레이스홀더가 파라미터 값으로 수용되었습니다. 그리고 결제자가 무엇을 받는지에 대해서는 알지 못합니다. 왜냐하면 나는 한 번도 결제해 본 적이 없기 때문입니다. 이제 주장은 관찰 가능한 사실로 제한됩니다: URL은 문서에 있는 문자열이며, 챌린지는 유효하고, 카탈로그는 이를 활성 서비스로 간주한다는 점입니다.
4. 나는 방금 내가 틀렸음을 증명한 기업들에 의도를 부여했다
나의 공개 피드(public feed)는 이름이 명시되고 식별 가능한 기업들에 대해 "미끼 상술 가격 책정 (bait-and-switch pricing)", "가격 폭리 (price gouging)", "허니팟 수신자 (honeypot receiver)", "해킹 가능성 (possible hijack)" 등의 라벨을 붙였습니다. 미국에서 앞의 두 용어는 법적 및 FTC (연방거래위원회) 측 의미를 담고 있으며, 네 가지 모두 의도 (intent)를 단정 짓는 표현입니다. 나는 HTTP 응답만으로는 의도를 증명할 수 없습니다. 그리고 나는 단 하루 만에 메커니즘에 대해 세 번이나 틀렸던 참이었습니다.
이제 레이블(Labels)은 관찰 내용을 설명합니다. "광고된 가격이 실제로 요청된 금액과 다름", "관찰 사이에 수신자 주소가 변경됨"과 같은 식이며, 정기적인 키 로테이션(key rotation)과 해킹(compromise)은 외부에서 구별할 수 없다는 참고 사항이 붙습니다. 이제 높은 가격은 "반환되는 내용에 대해 완전히 정당할 수 있음 — 우리는 공정성이 아니라 숫자를 측정함"이라는 의미를 갖습니다.
"라이브(live)"가 실제로 의미하는 것
이 모든 실수들은 _"402를 반환함"_을 _"지불 가능한 제품임"_과 같은 의미로 취급한 데서 비롯되었습니다. 그렇지 않습니다. 다음은 여러분의 인덱스(index)에도 적용할 수 있도록 작성한, 제가 현재 실행하는 체크리스트입니다. 그대로 복사해서 사용하세요.
| # | 확인 사항 | 존재하는 이유 |
|---|---|---|
| 1 | 바디(body)와 PAYMENT-REQUIRED 헤더 둘 다에서 챌린지(challenge)를 파싱하되, 헤더를 우선시할 것 | v2에서 위치가 이동됨; 바디만 읽는 리더는 라이브 서비스를 고장 난 것으로 인식함 |
| ... |
해당 체크리스트를 적용했을 때 나의 수치는 어떻게 변하는가
동일한 규율을 저 자신에게도 적용해 보았습니다. 현재 나의 인덱스는 3,487개의 라이브 엔드포인트(endpoints)를 보고하고 있습니다. 그 내부를 들여다보면 다음과 같습니다:
- 682개는 문서용 플레이스홀더(placeholders)입니다. 그리고 정직해지기 위해 다음 문장을 덧붙여야 합니다: 그중 582개는 단일 호스트인
api.gocreativeai.com입니다. 이것을 제외하면 22개 도메인에 걸친 100개의 플레이스홀더가 남습니다. 만약 "23개 도메인에 걸친 682개"라고 보고하고 거기서 멈춘다면, 그것은 바로 제가 비판하는 인플레이션(inflation) 그 자체일 것입니다. - 29개는 테스트 네트워크(test networks)에 있습니다.
- 1개는 스펙(spec) 외의 스킴(scheme)을 광고합니다.
- 1개는 바디/헤더 불일치(disagreement)가 있습니다.
정직한 나머지 수치: 338개 운영사(operators)에 걸친 2,775개 엔드포인트.
그리고 이 3,487개의 엔드포인트는 단 **350개의 고유 호스트(distinct hosts)**로 수렴하며, 상위 3개가 그중 1,575개를 차지합니다. 이는 엔드포인트 수치가 암시하는 것보다 훨씬 더 좁은 생태계임을 보여줍니다.
다른 한계점들을 솔직하게 말씀드리자면, 제 관측은 2026-07-06부터 시작되었으며 이는 매우 얕은 범위입니다. 다른 트래커들은 더 깊은 이력과 더 넓은 범위를 가지고 있습니다 — x402.fuchss.app은 약 97k개의 상장된 엔드포인트(endpoints)에 대해 2025-05-09부터 완료된 온체인 데이터(on-chain data)를 게시하며, 도달 가능성(reachability)과 엔벨로프 준수(envelope compliance)를 분리하여 다룹니다. 이는 정확히 제가 틀렸던 구분점입니다. 우리의 수치가 서로 다를 경우, 더 긴 시계열 데이터가 추세(trends)에 대해 맞을 가능성이 더 높습니다. 저는 오늘 다시 조사(re-probe)할 수 있는 내용에 대해서만 확신합니다.
여기서 모든 것을 직접 검증하세요
# 생태계 총계, 구성, 카탈로그 비교
curl -s https://pulsefeed.dev/status.json | jq '.ecosystem, .catalogAudit'
...
데이터셋은 Hugging Face에서 CC-BY-4.0 라이선스로 제공됩니다. 원문 기사 상단에는 이제 수정 공지(correction notice)가 포함되어 있으며, 이전 프로버(prober)가 생성한 일일 아카이브 보고서에는 독자들에게 활성 수치(liveness numbers)를 인용하지 말라는 배너가 표시되어 있습니다. 또한 /llms.txt에는 링크 옆에 기계 판독 가능한(machine-readable) 수정 사항이 포함되어 있습니다.
만약 당신이 카탈로그(catalog), 지갑(wallet), 또는 엔드포인트에 비용을 지불할지 결정하는 에이전트(agent)를 운영한다면: 오늘 바로 1번 체크(check #1)를 실행해 보세요. 단 10줄의 코드이며, 당신의 대시보드에서 이것은 살아있는 생태계와 거의 죽어있는 생태계를 가르는 차이가 될 것입니다.
이 포스트에서 오류를 발견한다면 저에게 알려주세요. 그것이 이제 이 데이터셋을 개선하는 가장 빠른 방법임이 입증되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기