Build third-party API integrations with Claude Code
PLClaude Code builds production-grade third-party integrations (Stripe payments, transactional email, OAuth 2.0 login and inbound webhooks) when the prompt names the events, the idempotency key and the failure categories up front. Vendor plugins or MCP servers supply current API documentation, and replayed test events, signature tests and duplicate-delivery tests prove the result without a line-by-line review.
You need Stripe subscriptions live by Thursday. The quickstart charge works in an hour; then come duplicate webhook deliveries, events that arrive before the redirect, a proxy that breaks signature checks, and a dunning email nobody wrote. This page is for developers wiring external services into an app with Claude Code, and for tech leads who want every integration to pass the same checks before it reaches main.
What you’ll walk away with from building integrations in Claude Code
Section titled “What you’ll walk away with from building integrations in Claude Code”- A setup that gives Claude Code current vendor docs through Stripe’s and Resend’s own plugins, or Context7 for any other library.
- An event matrix: the one-page contract a human reviews instead of the handler code.
- Five copy-paste prompts: the event matrix, a test-first Stripe slice, transactional email, OAuth 2.0 login with PKCE, and a shared webhook inbox.
- A verification stack (signed fixtures, duplicate and out-of-order replays, the Stripe CLI and a Stop hook) that proves the integration without reading every line.
- The failure modes that reach production, each with its recovery.
How do you give Claude Code current vendor docs?
Section titled “How do you give Claude Code current vendor docs?”Payment and email APIs change faster than any model’s training data. An agent that writes against last year’s API produces code that compiles and then fails at runtime. Give the session a live source before the first prompt. Three routes, from most to least specific:
| Route | What it adds | Install (terminal) | Use it when |
|---|---|---|---|
| Stripe plugin | Stripe’s skills (stripe-best-practices, upgrade-stripe and others) plus its MCP server, updated automatically | claude plugin install stripe@claude-plugins-official | Anything that touches Stripe |
| Resend plugin | Resend’s skills (resend, react-email, email-best-practices and others) plus the hosted Resend MCP server (OAuth) | claude plugin marketplace add resend/resend-skills, then claude plugin install resend@resend-skills | Transactional email through Resend |
| Context7 MCP | Version-specific docs for most public libraries | claude mcp add --transport http context7 https://mcp.context7.com/mcp | Any provider without its own plugin (the API key is optional and raises rate limits) |
Install commands come from each vendor’s repository: stripe/ai, resend/resend-skills and upstash/context7. For scopes and .mcp.json placement, see MCP setup for Claude Code. For the Context7 tool list and usage, see Context7. The workflow on this page carries over to other agents unchanged; only the install differs. In Codex, the Stripe plugin is codex plugin add stripe@openai-curated (see MCP setup for Codex), and Cursor users start from MCP setup for Cursor.
If your team prefers a local Stripe MCP server in the repository’s .mcp.json, reference the key instead of pasting it. The server reads STRIPE_SECRET_KEY from its environment, and Claude Code expands ${VAR} at startup:
claude mcp add stripe --scope project \ -e 'STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}' \ -- npx -y @stripe/mcp@0.3.3Which model? Stay on the Claude Code default, Claude Opus 5.5 (on both release channels on 2026-10-12), which runs at medium effort. Raise it with /effort high before you switch models. Check /model to confirm what your install runs. Choose Claude Fable 5.1 deliberately (/model fable) only for a long multi-provider rewrite that your own runs show Opus 5.5 cannot finish. Prices and defaults per plan are on the models hub.
Write the event matrix before any handler
Section titled “Write the event matrix before any handler”Most integration bugs are state bugs, not HTTP bugs: an event processed twice, an event that arrives before the row it updates, a retry that sends a second email. The fix is a contract that names every inbound event, its idempotency key, the state transition it may make and its side effects. A human reviews this table, not the 800 lines that implement it.
| Event | Idempotency key | Allowed transition | Side effects | Retry on failure? ||--------------------------------|-----------------|-----------------------------|-------------------------------|-------------------|| checkout.session.completed | event.id | order pending -> paid | grant entitlement, receipt | yes (5xx, network)|| invoice.payment_failed | event.id (email: invoice.id) | subscription active -> past_due | dunning email (once per invoice) | yes || customer.subscription.deleted | event.id | any -> cancelled | revoke entitlement | yes || charge.refunded | event.id | paid -> refunded | revoke entitlement, notify | yes || anything else | — | none | log and return 200 | no |Add two lines to CLAUDE.md so every session honours the contract: “Integration contracts live in docs/integrations/. Change the table first, in its own commit, and never add a handler for an event that is not in it.” See how Claude Code’s memory files load.
Build a Stripe payment integration step by step
Section titled “Build a Stripe payment integration step by step”-
Draft the event matrix in plan mode.
Start with
claude --permission-mode plan, or type/planin a running session. Claude Code reads the code and proposes, but does not edit.Review the table, answer the open questions, and commit it on its own.
-
Implement one slice, tests first.
Leave plan mode with Shift+Tab. The tests are written against Stripe’s own signing helper, so they prove signature handling rather than mocking it away.
-
Replay real events against the running app.
Unit tests prove the logic; the Stripe CLI proves the wiring. In one terminal:
Terminal window stripe listen --forward-to http://localhost:3000/api/webhooks/stripestripe listenprints a webhook signing secret (whsec_…) at startup. Put it in your local.envasSTRIPE_WEBHOOK_SECRET, restart the app, then in a second terminal:Terminal window stripe trigger checkout.session.completedstripe trigger invoice.payment_failedEvery trigger should appear in the
stripe listenoutput with a200, and once inwebhook_events. -
Close the edge cases the matrix names.
The happy path passes. Now handle: 1) the webhook arrives before theCheckout redirect returns, so the order row may still be pending;2) a failed subscription payment sends a dunning email through ouremail service at most once per invoice; 3) a refund updates the orderand revokes the entitlement. Add a failing test for each case first.
Add transactional email with Resend
Section titled “Add transactional email with Resend”Email looks trivial until a retry sends two password-reset links, or a bounce loop gets your domain throttled. Ask for the failure behaviour explicitly, and keep development mail out of real inboxes.
Do not hard-code a requests-per-second figure from memory. Read the current limit for your account in the Resend dashboard and put it in docs/integrations/resend.md, so the agent uses the documented number.
Add OAuth 2.0 login with Google and GitHub
Section titled “Add OAuth 2.0 login with Google and GitHub”OAuth mistakes are security bugs, so the prompt names the protections, not only the flow. Two defaults matter: validate state on every callback, and use the authorization code flow with PKCE for mobile and single-page clients. Never return tokens in a URL fragment, which is the old implicit flow that the OAuth 2.0 Security Best Current Practice (RFC 9700) advises against.
Route every provider’s webhooks through one inbox
Section titled “Route every provider’s webhooks through one inbox”Once you receive webhooks from three providers, give them one pipeline: verify, store, deduplicate, process, dead-letter. A new integration then adds a verifier and a handler, not a new set of failure modes.
For the reverse direction, sending signed webhooks to your own customers, see build REST and GraphQL APIs with Claude Code.
Should Claude Code use the vendor SDK or generate a client?
Section titled “Should Claude Code use the vendor SDK or generate a client?”| Situation | Choose | Why |
|---|---|---|
| The vendor ships a maintained official SDK (Stripe, Resend) | The SDK | It carries signature verification, retries and API-version pinning that a generated client would have to reinvent |
| No official SDK for your language or runtime, or the SDK does not run on your edge runtime | A generated typed fetch client | Small, visible HTTP calls; ask for typed errors that carry the vendor’s error code |
| You call two or three endpoints of a large API | A generated client for those endpoints only | Avoids a heavy dependency for a narrow use |
| Security-sensitive verification (webhook signatures, JWT validation) | Never hand-rolled if the SDK or a vetted library offers it | Constant-time comparison and clock-skew handling are easy to get subtly wrong |
How do you prove an integration works without reading every line?
Section titled “How do you prove an integration works without reading every line?”Review the event matrix and the tests; let the gates check the code. A merge-ready integration passes all of these:
- Signed fixture tests for every row of the matrix: valid, missing and tampered signatures; duplicate delivery; out-of-order delivery; a throwing handler returning 5xx. The tests sign payloads with the vendor’s helper, so a mocked-away verifier fails them.
- A live replay with
stripe triggerfor each handled event, checked instripe listenoutput and in the inbox table. - A Stop hook that runs the integration suite and blocks the session from finishing on red (the configuration follows this list).
/code-reviewand/security-reviewin the session before you open the pull request. Both are built into Claude Code: the first reviews the diff for correctness, the second runs a security-focused pass over the same changes.- Production signals: every processed event logs provider, event ID and outcome, and an alert fires when the dead-letter count is above zero. See monitoring with Claude Code.
The Stop hook turns “the integration works” into a claim backed by a test run. Save the script, make it executable with chmod +x, and register it in .claude/settings.json:
#!/usr/bin/env bash# .claude/hooks/integration-tests.sh (Stop)set -uo pipefailinput=$(cat)if out=$(npm test --silent -- tests/integrations </dev/null 2>&1); then exit 0; fiif [[ $(jq -r '.stop_hook_active' <<<"$input") == "true" ]]; then jq -nc '{systemMessage: "Integration tests still fail after one retry. Stopping for a human."}' exit 0fijq -nc --arg r "Integration tests fail. Fix them before you finish. Last 40 lines:$(tail -n 40 <<<"$out")" '{decision: "block", reason: $r}'{ "hooks": { "Stop": [ { "hooks": [{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/integration-tests.sh", "args": [], "timeout": 300 }] } ] }}The hook blocks the first stop on red tests and hands over to you if the retry also fails. For exit codes, the stop_hook_active flag and a version that skips a clean tree, see Claude Code hooks. Why test gates replace line-by-line review is the subject of reading evidence instead of code.
Sign-off: the developer owns the tests and the replay; the tech lead approves the event matrix and the dead-letter alert. The only code a human reads line by line is signature verification and anything that moves money.
When third-party integrations break
Section titled “When third-party integrations break”Signature verification passes locally and fails in production. A body parser or proxy changed the raw bytes before the verifier saw them. Recovery: “Mount the webhook route with express.raw before express.json, and add a test that sends a pretty-printed copy of a signed payload and expects 400.” Then check that the load balancer does not rewrite bodies.
The agent wrote against an outdated API. Parameter names or event shapes do not match what the vendor sends. Recovery: install the vendor plugin or Context7, pin the API version in docs/integrations/, and ask: “Compare src/services/stripe.ts with the current Stripe API version using the plugin’s skills and list every call that changed.” Stripe’s upgrade-stripe skill covers version upgrades.
Tests pass because verification was mocked. A generated test stubs constructEvent to return the payload, so the suite is green for any signature. Recovery: run the audit prompt above; its “verifier always true” step catches this.
Events arrive out of order. invoice.payment_failed lands before checkout.session.completed. Recovery: “Make every handler check the current state and apply only the transitions the event matrix allows; add a test per out-of-order pair.”
One webhook produced two emails or two grants. Idempotency was keyed on something other than the event ID, or the side effect ran before the inbox write committed. Recovery: “Write the inbox row and the side effect in one transaction, or make the side effect idempotent with its own key; add a duplicate-delivery test that fails today.”
A batch job hits the provider’s rate limit. Recovery: “Add a concurrency limiter to the client using the rate documented in docs/integrations/, retry 429 with backoff that honours Retry-After, and add a test with a mocked 429.”
The agent used a live key through MCP. Recovery: revoke the key in the vendor dashboard at once, review the vendor’s event log for actions taken, and switch the session to a test-mode restricted key. See MCP security.
Where to go next with integrations
Section titled “Where to go next with integrations”Prerequisite: MCP setup for Claude Code. Next: testing with Claude Code extends the signed-fixture approach to the rest of the suite.