Skip to content

feat(mpp): Makes mpp pay handle bot-blocked merchants transparently#110

Open
nvp-stripe wants to merge 3 commits into
mainfrom
nvp/link-cli-mpp-bot-auth-bypass
Open

feat(mpp): Makes mpp pay handle bot-blocked merchants transparently#110
nvp-stripe wants to merge 3 commits into
mainfrom
nvp/link-cli-mpp-bot-auth-bypass

Conversation

@nvp-stripe
Copy link
Copy Markdown
Contributor

@nvp-stripe nvp-stripe commented May 21, 2026

Summary

Makes mpp pay handle bot-blocked merchants transparently. When the initial probe to a merchant URL returns 403, the CLI now fetches Web Bot Auth headers automatically, retries the probe with Signature + Signature-Input headers attached, and continues the SPT payment flow if the retry returns 402.

Before this PR: agents hitting a Cloudflare-protected merchant had to manually call web-bot-auth sign <url> and inject the headers themselves.mpp pay would return a raw 403 and the payment would fail.

After this PR: mpp pay handles the full 403 => bot-bypass => 402 => SPT chain in one command. No manual web-bot-auth sign call needed.

New retry logic (both runMppPay and MppPay component)

1. Probe URL
   └── 200/4xx/5xx (not 403, not 402) → return response as-is
   └── 403 (bot-blocked)
         ├── call WebBotAuthResource.getHeaders(url)
         ├── retry probe with Signature + Signature-Input headers
         │   └── 200/4xx/5xx (not 402) → return response as-is
         │   └── 402 (Payment Required)
         │         ├── parse WWW-Authenticate stripe challenge
         │         └── retry with bot auth headers + Authorization: Payment <spt>
         └── 402 (first probe was 403, retry was not 403 or 402) → handled above
   └── 402 (no bot-blocking, direct payment challenge)
         ├── parse WWW-Authenticate stripe challenge
         └── retry with Authorization: Payment <spt>

Implementation notes

  • webBotAuth: IWebBotAuthResource threaded through 7 callers: createMppCli, MppPay, runMppPay, SptFlow (demo), DemoRunner, createDemoCli, OnboardRunner, createOnboardCli. All wired from factory.createWebBotAuthResource() in cli.tsx.

  • botAuthHeaders accumulator: a Record<string, string> initialized to {} before the 403 branch. If a 403 is encountered, it is populated with Signature and Signature-Input. It is spread into every subsequent fetch (the bot-bypass retry and the final SPT retry), so the merchant always sees both headers on any credentialed request.

  • New bypassing step added to the Step type for interactive progress display: 'retrieving' | 'probing' | 'bypassing' | 'signing' | 'submitting' | 'done'.

  • Both runMppPay and MppPay updated in parallel: runMppPay is the function-level API used by tests and JSON mode; MppPay is the Ink component used in interactive mode. The logic is intentionally duplicated (pre-existing
    design, not introduced here) - refactoring into a shared hook is a separate task.

  • Depends on WebBotAuthResource (WebBotAuthResource in SDK, merged as feat(sdk): add WebBotAuthResource for Web Bot Auth header minting #105) and
    web-bot-auth sign command, merged as Add web-bot-auth sign <url> command #106. This branch is based on main after both merged.

Motivation

mpp pay is the one-command path for agent-initiated payments. Requiring agents to separately call web-bot-auth sign before mpp pay defeats the purpose of a single-command payment flow, especially in MCP/tool-use contexts where orchestration overhead matters.

Making bot bypass automatic in mpp pay means:

  • Agents using mpp pay get Cloudflare bypass for free, no prompt engineering needed.
  • web-bot-auth sign remains available as a standalone tool for agents that
    need the headers for non-mpp pay flows (e.g., streaming checkouts, custom
    HTTP clients).

What's coming next

  • Update SKILL.md with browsing instructions: documents web-bot-auth sign and mpp pay's bot-bypass behavior so LLMs discover and use them correctly.

Testing

Unit tests

  • packages/cli/src/commands/mpp/__tests__/pay.test.ts

The full-chain test (403 => 402 => SPT) uses a real mppx

  • Mppx.create({...}).createCredential(response) call against a properly formatted WWW-Authenticate fixture: no mocking of the credential library.
  • This confirms the header accumulation is correct end-to-end.

… bypass)

When `mpp pay` receives a 403, it now calls `webBotAuth.getHeaders()`,
attaches `Signature` + `Signature-Input` headers, and retries the probe.
If the retry returns 402, the SPT flow continues with both header sets
carried through to the final payment request.

- `runMppPay` and `MppPay` component both gain the `webBotAuth` param
- New `bypassing` step label for interactive progress display
- 7-test unit suite for `runMppPay` including the full 403→402→SPT flow
- CLAUDE.md updated with the 2-step auto-retry behavior

Committed-By-Agent: claude
@nvp-stripe nvp-stripe requested a review from a team as a code owner May 21, 2026 01:23
Copy link
Copy Markdown
Contributor

@raubrey-stripe raubrey-stripe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A request could in theory return a 403 for a few reasons not just due to lack of web bot auth. How do we determine it's because of lack of web bot auth? If we can't, what do you think about trying to include the signature/signature-input headers of each MPP request? Seems like little downside to just automatically including them?

@nvp-stripe
Copy link
Copy Markdown
Contributor Author

A request could in theory return a 403 for a few reasons not just due to lack of web bot auth. How do we determine it's because of lack of web bot auth? If we can't, what do you think about trying to include the signature/signature-input headers of each MPP request? Seems like little downside to just automatically including them?

Ah yes - good question on the 403 ambiguity. That said, I'd push back a bit on proactively including the headers on every request. Getting WBA headers requires a round-trip to the link api; so if I'm understanding your point, we'd be adding that latency to every mpp pay call even for merchants with no bot protection. The cache helps on repeat calls to the same domain but the first call still pays the cost. If that endpoint is slow or has an issue it would break mpp pay for everyone, not just for bot-blocked flows.

The underlying gap I think you're pointing at is what happens when the WBA retry also gets a 403. Right now that probably surfaces a confusing error. I think the better fix is to explicitly distinguish that case: if we retry with WBA headers and I think the better fix is to explicitly distinguish that case: if we retry with WBA headers and still get a 403, fail with a clear message with something like the bot bypass didn't work and this is likely a different kind of 403. That way, agents get the right signal without the happy path paying a tax for merchants that don't need WBA at all.

lmk what you think? Happy to make the retry failure path more explicit if that addresses the concern.

@raubrey-stripe
Copy link
Copy Markdown
Contributor

lmk what you think? Happy to make the retry failure path more explicit if that addresses the concern.

chatting offline - my take is that latency here isn't a concern and we should keep this surface area simple. Automatic is better and no one interacting with mpp pay will notice/care about the milliseconds, they'll appreciate link doing smart thigns on their behalf to ensure the traffic is prepped in the smartest way possible!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants