<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: FetchSandbox</title>
    <description>The latest articles on Forem by FetchSandbox (@fetchsandbox).</description>
    <link>https://forem.com/fetchsandbox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3830177%2Fe63c5a3c-3f48-4ae2-ad6b-ed510712a396.png</url>
      <title>Forem: FetchSandbox</title>
      <link>https://forem.com/fetchsandbox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/fetchsandbox"/>
    <language>en</language>
    <item>
      <title>Testing Resend's template email flow end to end</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Sat, 25 Apr 2026 14:06:02 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/testing-resends-template-email-flow-end-to-end-44k3</link>
      <guid>https://forem.com/fetchsandbox/testing-resends-template-email-flow-end-to-end-44k3</guid>
      <description>&lt;p&gt;&lt;em&gt;Three API calls. One template ID that has to carry through all of them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Resend's API for sending a templated email is three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a template&lt;/li&gt;
&lt;li&gt;Publish the template&lt;/li&gt;
&lt;li&gt;Send an email using that template&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step depends on the one before it.&lt;/p&gt;

&lt;p&gt;The template ID from step 1 has to be passed into step 2. The published status from step 2 has to be confirmed before step 3 will actually send. And if you want to verify your integration end to end, you need the &lt;code&gt;template.created&lt;/code&gt; webhook to fire after step 1.&lt;/p&gt;

&lt;p&gt;That is a simple flow. But most developers do not actually test it as a flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  How most people test this
&lt;/h2&gt;

&lt;p&gt;The typical approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a template in the Resend dashboard manually&lt;/li&gt;
&lt;li&gt;Copy the template ID into your code&lt;/li&gt;
&lt;li&gt;Call the send endpoint with that hardcoded ID&lt;/li&gt;
&lt;li&gt;Check your inbox&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That tests one thing: can your code call POST /emails with a valid template ID?&lt;/p&gt;

&lt;p&gt;It does not test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether your code correctly creates templates via the API&lt;/li&gt;
&lt;li&gt;Whether the publish step returns the right status&lt;/li&gt;
&lt;li&gt;Whether the template ID chains correctly from creation to sending&lt;/li&gt;
&lt;li&gt;Whether the template.created webhook fires and your handler processes it&lt;/li&gt;
&lt;li&gt;Whether the whole flow works when run programmatically, not manually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hardcoding a template ID is not integration testing. It is calling one endpoint with a known parameter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the real flow looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /templates
  → 200: { id: "tpl_abc123", object: "template" }
  → webhook: template.created fires

POST /templates/tpl_abc123/publish
  → 200: { id: "tpl_abc123", status: "published" }
  → webhook: template.published fires

POST /emails
  → 200: { id: "email_xyz789" }
  body: { template_id: "tpl_abc123", to: "user@example.com" }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three calls. Two webhooks. The template ID threads through every step.&lt;/p&gt;

&lt;p&gt;The question you should be able to answer before shipping is not "does POST /emails work?" It is "does my entire template-to-send pipeline work when the template is created, published, and referenced programmatically?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The webhook part
&lt;/h2&gt;

&lt;p&gt;Most email integrations skip webhook verification entirely.&lt;/p&gt;

&lt;p&gt;That is understandable. For a simple "send one email" integration, you might not need webhooks at all.&lt;/p&gt;

&lt;p&gt;But once you are building a system that creates templates dynamically — onboarding flows, transactional sequences, marketing automation — you probably want to know when a template was created and when it was published.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;template.created&lt;/code&gt; tells your system the template exists. &lt;code&gt;template.published&lt;/code&gt; tells your system it is safe to reference in send calls.&lt;/p&gt;

&lt;p&gt;If your code sends an email with a template that has not finished publishing, you get a confusing error that looks like a bad template ID. The actual issue is a timing problem in your flow.&lt;/p&gt;

&lt;p&gt;Testing the webhooks is how you catch this before production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The testing setup problem
&lt;/h2&gt;

&lt;p&gt;To test this flow against real Resend, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Resend API key&lt;/li&gt;
&lt;li&gt;A verified sender domain&lt;/li&gt;
&lt;li&gt;A real email address to receive the test send&lt;/li&gt;
&lt;li&gt;A webhook endpoint reachable from the internet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is reasonable for a production integration test.&lt;/p&gt;

&lt;p&gt;It is less reasonable for the Tuesday afternoon when you just want to know if your template creation flow works before you wire it into your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it without any of that
&lt;/h2&gt;

&lt;p&gt;I built a sandbox where you can run this exact flow without a Resend API key, without a verified domain, and without hosting a webhook endpoint.&lt;/p&gt;

&lt;p&gt;You call POST /templates. The sandbox creates a template and returns a real ID. You call POST /templates/{id}/publish. The status updates. You call POST /emails with that template ID. The send succeeds. The webhooks fire automatically between steps.&lt;/p&gt;

&lt;p&gt;The state persists. The IDs chain. The whole flow runs in about 30 seconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fetchsandbox.com/docs/resend?page=workflow-template_email_workflow" rel="noopener noreferrer"&gt;Try the Resend template workflow&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for email integrations specifically
&lt;/h2&gt;

&lt;p&gt;Email is one of those integrations where "it worked in testing" and "it works in production" can be very different things.&lt;/p&gt;

&lt;p&gt;Templates get created but not published. Send calls reference stale template IDs. Webhook handlers assume the template exists before the creation event arrives.&lt;/p&gt;

&lt;p&gt;These are not exotic edge cases. They are the normal bugs that happen when you test endpoints in isolation instead of testing the flow.&lt;/p&gt;

&lt;p&gt;The fix is not complicated. Run the whole chain once, in order, with state that carries between steps. If the template ID from step 1 appears correctly in step 3, your integration probably works. If it does not, you want to find out now, not when your onboarding emails start failing.&lt;/p&gt;

&lt;p&gt;That is what &lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;FetchSandbox&lt;/a&gt; does. Turns any OpenAPI spec into a stateful sandbox with workflows.&lt;/p&gt;

&lt;p&gt;Curious how other people handle email integration testing. Do you test against real Resend with test keys, use a mock, or mostly just trust the dashboard and check your inbox?&lt;/p&gt;

</description>
      <category>email</category>
      <category>api</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to test GitHub merge conflicts locally</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Wed, 22 Apr 2026 04:47:25 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/how-to-test-github-merge-conflicts-locally-35d0</link>
      <guid>https://forem.com/fetchsandbox/how-to-test-github-merge-conflicts-locally-35d0</guid>
      <description>&lt;p&gt;&lt;em&gt;The happy path for a pull request is boring. The merge conflict is where the integration usually gets real.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most GitHub API demos stop too early.&lt;/p&gt;

&lt;p&gt;They show how to open the pull request, fetch it, maybe list reviews, and then call it done.&lt;/p&gt;

&lt;p&gt;That is useful for proving your auth works. It is not enough for proving your PR automation works.&lt;/p&gt;

&lt;p&gt;The harder branch is the one where the pull request exists, the metadata looks fine, and then the merge step hits a conflict. That is the path that decides whether your bot, internal tool, or release workflow can recover sanely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small workflow that actually matters
&lt;/h2&gt;

&lt;p&gt;For this kind of test, I care about a very small flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;open the pull request&lt;/li&gt;
&lt;li&gt;fetch the PR again to inspect its status&lt;/li&gt;
&lt;li&gt;try the merge step&lt;/li&gt;
&lt;li&gt;see what your system does when the PR is not cleanly mergeable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That starts with something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.github.com/repos/acme-corp/api-gateway/pulls"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/vnd.github+json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "title": "fix: handle nil pointer in rate limiter",
    "body": "Fixes #142. Adds nil check before accessing connection pool.",
    "head": "fix/rate-limiter-nil",
    "base": "main"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then quickly becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.github.com/repos/acme-corp/api-gateway/pulls/&amp;lt;number&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/vnd.github+json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The useful part is not just that the PR exists. It is what your code does when the branch cannot merge cleanly after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where teams usually get fooled
&lt;/h2&gt;

&lt;p&gt;The trap is treating "pull request created" as proof that the automation is basically done.&lt;/p&gt;

&lt;p&gt;But a lot of GitHub workflows only become interesting after creation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the branch is stale&lt;/li&gt;
&lt;li&gt;another change landed on &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the PR looks valid but is not mergeable&lt;/li&gt;
&lt;li&gt;your bot tries to continue anyway&lt;/li&gt;
&lt;li&gt;your UI says "ready" even though a human now has to intervene&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the same pattern that shows up in a lot of async integrations: step 1 succeeds, so the app assumes the whole job is healthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would test before production
&lt;/h2&gt;

&lt;p&gt;If I only had time for one narrow GitHub API test, I would test the merge-conflict branch.&lt;/p&gt;

&lt;p&gt;I would check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;can my app detect that the PR exists but is not safely mergeable?&lt;/li&gt;
&lt;li&gt;do I expose the right state to the user, instead of pretending it is still a normal merge path?&lt;/li&gt;
&lt;li&gt;do retries keep hammering the same PR, or do we stop and surface the conflict clearly?&lt;/li&gt;
&lt;li&gt;if there is a webhook or follow-up read in the flow, does the final state stay honest?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last part matters more than the first API call.&lt;/p&gt;

&lt;p&gt;A lot of automation bugs are not "could not create PR."&lt;/p&gt;

&lt;p&gt;They are more like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"we created it, then misread the later state"&lt;/li&gt;
&lt;li&gt;"we retried the wrong step"&lt;/li&gt;
&lt;li&gt;"we told the user merge was blocked too late"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this is a better test than another PR happy path
&lt;/h2&gt;

&lt;p&gt;A happy-path pull request test mostly proves you can talk to GitHub.&lt;/p&gt;

&lt;p&gt;A merge-conflict test tells you whether your integration can survive a normal repo state that changes under it.&lt;/p&gt;

&lt;p&gt;That is a much better signal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;release tooling&lt;/li&gt;
&lt;li&gt;AI coding agents opening PRs&lt;/li&gt;
&lt;li&gt;internal automation that tries to auto-merge fixes&lt;/li&gt;
&lt;li&gt;any workflow that assumes the branch state will stay clean between creation and merge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is also more realistic. Real repositories drift constantly. If your testing never touches the conflict branch, your merge logic is probably getting too much credit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I think is underrated
&lt;/h2&gt;

&lt;p&gt;The useful question is not just:&lt;/p&gt;

&lt;p&gt;"can I create a PR through the GitHub API?"&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;p&gt;"when the PR stops being mergeable, do I still know what happened and what to do next?"&lt;/p&gt;

&lt;p&gt;That is the gap between endpoint testing and workflow testing.&lt;/p&gt;

&lt;p&gt;The endpoint docs explain the request shape. The real integration job is deciding what your system should do after the branch state changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to make this easier locally
&lt;/h2&gt;

&lt;p&gt;I like testing this as a narrow workflow instead of a broad "GitHub API integration" check.&lt;/p&gt;

&lt;p&gt;That is also why I keep a runnable &lt;a href="https://fetchsandbox.com/docs/github" rel="noopener noreferrer"&gt;GitHub docs portal on FetchSandbox&lt;/a&gt; around. The useful part is being able to open the PR, fetch it again, and inspect the later branch in one place instead of stopping at creation.&lt;/p&gt;

&lt;p&gt;For me, the merge-conflict path is one of those cases where a small failure-mode test teaches more than a longer happy-path demo.&lt;/p&gt;

&lt;p&gt;Curious how other people handle this. If your GitHub automation opens a PR and later hits a conflict, do you surface that as a first-class state, or does it still turn into log-diving and confused retries?&lt;/p&gt;

</description>
      <category>github</category>
      <category>api</category>
      <category>testing</category>
      <category>devtools</category>
    </item>
    <item>
      <title>How to test Stripe webhook signatures locally without breaking verification</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Fri, 17 Apr 2026 16:24:17 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/how-to-test-stripe-webhook-signatures-locally-without-breaking-verification-1236</link>
      <guid>https://forem.com/fetchsandbox/how-to-test-stripe-webhook-signatures-locally-without-breaking-verification-1236</guid>
      <description>&lt;p&gt;&lt;em&gt;The request usually succeeds first. The signature check is where the time disappears.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most Stripe integrations do not fail on the first API call.&lt;/p&gt;

&lt;p&gt;You create the customer. You create the PaymentIntent. You confirm it. Everything looks fine. Then the webhook arrives and your handler says &lt;code&gt;invalid signature&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is usually the moment the debugging session starts.&lt;/p&gt;

&lt;p&gt;The annoying part is that the failure often has nothing to do with Stripe itself. It is usually your local setup. The payload got parsed too early. The raw body changed. The signature was generated against bytes your app never saw.&lt;/p&gt;

&lt;p&gt;One common example in Node/Express is using &lt;code&gt;express.json()&lt;/code&gt; on the webhook route. That middleware parses and re-serializes the body, which means the bytes you verify are no longer the bytes Stripe signed.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;webhookSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you usually need the raw body on that route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;webhookSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hard part is not just fixing the route once. It is trusting that the whole flow still works after the fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the webhook arrives&lt;/li&gt;
&lt;li&gt;the signature verifies&lt;/li&gt;
&lt;li&gt;your handler updates state correctly&lt;/li&gt;
&lt;li&gt;retries do not double-process the event&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why webhook testing feels weirdly slippery. The API call and the webhook handler are two different systems, and most local setups only make one of them easy to observe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to test instead of the webhook in isolation
&lt;/h2&gt;

&lt;p&gt;What has helped me most is testing the full flow instead of testing the webhook in isolation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;trigger the upstream action&lt;/li&gt;
&lt;li&gt;inspect the exact webhook payload and headers&lt;/li&gt;
&lt;li&gt;verify the handler against the raw body&lt;/li&gt;
&lt;li&gt;confirm the final state change after the webhook is processed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last step matters more than most examples admit. A verified signature is good. A verified signature plus the correct final state is what actually tells you the integration works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this bug sticks around
&lt;/h2&gt;

&lt;p&gt;Webhook bugs are slippery because the API call and the webhook handler live on two separate timelines.&lt;/p&gt;

&lt;p&gt;The API request can succeed immediately. The event can arrive later. Your logs for one may be clean while the other is quietly failing. That is why developers end up jumping between local tunnels, dashboard events, request logs, and app state, trying to piece together what actually happened.&lt;/p&gt;

&lt;p&gt;The hard part is rarely "can I trigger a Stripe event?" The hard part is "can I trust the whole PaymentIntent plus webhook path enough to ship it?"&lt;/p&gt;

&lt;h2&gt;
  
  
  A more useful local testing setup
&lt;/h2&gt;

&lt;p&gt;If you want a shortcut, use a &lt;a href="https://fetchsandbox.com/webhook-sandbox" rel="noopener noreferrer"&gt;webhook sandbox&lt;/a&gt; or a test environment that lets you inspect the full Stripe flow end-to-end instead of only replaying one event at a time.&lt;/p&gt;

&lt;p&gt;The useful part is not the mock payload. It is being able to see the request, the event, and the resulting state in one place.&lt;/p&gt;

&lt;p&gt;For Stripe-specific workflow context, this is also why a runnable &lt;a href="https://fetchsandbox.com/docs/stripe" rel="noopener noreferrer"&gt;Stripe portal&lt;/a&gt; is more useful than example requests alone.&lt;/p&gt;

&lt;p&gt;Curious what other people use here. Do you mostly replay events, tunnel to localhost, or test the full PaymentIntent plus webhook path every time?&lt;/p&gt;

</description>
      <category>stripe</category>
      <category>webhooks</category>
      <category>api</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to test Stripe webhook signatures locally without breaking verification</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Wed, 15 Apr 2026 18:51:30 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/how-to-test-stripe-webhook-signatures-locally-without-breaking-verification-5ggk</link>
      <guid>https://forem.com/fetchsandbox/how-to-test-stripe-webhook-signatures-locally-without-breaking-verification-5ggk</guid>
      <description>&lt;p&gt;&lt;em&gt;The request usually succeeds first. The signature check is where the time disappears.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most Stripe integrations do not fail on the first API call.&lt;/p&gt;

&lt;p&gt;You create the customer. You create the PaymentIntent. You confirm it. Everything looks fine. Then the webhook arrives and your handler says &lt;code&gt;invalid signature&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is usually the moment the debugging session starts.&lt;/p&gt;

&lt;p&gt;The annoying part is that the failure often has nothing to do with Stripe itself. It is usually your local setup. The payload got parsed too early. The raw body changed. The signature was generated against bytes your app never saw.&lt;/p&gt;

&lt;p&gt;One common example in Node/Express is using &lt;code&gt;express.json()&lt;/code&gt; on the webhook route. That middleware parses and re-serializes the body, which means the bytes you verify are no longer the bytes Stripe signed.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;webhookSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you usually need the raw body on that route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;webhookSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hard part is not just fixing the route once. It is trusting that the whole flow still works after the fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the webhook arrives&lt;/li&gt;
&lt;li&gt;the signature verifies&lt;/li&gt;
&lt;li&gt;your handler updates state correctly&lt;/li&gt;
&lt;li&gt;retries do not double-process the event&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why webhook testing feels weirdly slippery. The API call and the webhook handler are two different systems, and most local setups only make one of them easy to observe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to test instead of the webhook in isolation
&lt;/h2&gt;

&lt;p&gt;What has helped me most is testing the full flow instead of testing the webhook in isolation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;trigger the upstream action&lt;/li&gt;
&lt;li&gt;inspect the exact webhook payload and headers&lt;/li&gt;
&lt;li&gt;verify the handler against the raw body&lt;/li&gt;
&lt;li&gt;confirm the final state change after the webhook is processed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last step matters more than most examples admit. A verified signature is good. A verified signature plus the correct final state is what actually tells you the integration works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this bug sticks around
&lt;/h2&gt;

&lt;p&gt;Webhook bugs are slippery because the API call and the webhook handler live on two separate timelines.&lt;/p&gt;

&lt;p&gt;The API request can succeed immediately. The event can arrive later. Your logs for one may be clean while the other is quietly failing. That is why developers end up jumping between local tunnels, dashboard events, request logs, and app state, trying to piece together what actually happened.&lt;/p&gt;

&lt;p&gt;The hard part is rarely "can I trigger a Stripe event?" The hard part is "can I trust the whole PaymentIntent plus webhook path enough to ship it?"&lt;/p&gt;

&lt;h2&gt;
  
  
  A more useful local testing setup
&lt;/h2&gt;

&lt;p&gt;If you want a shortcut, use a &lt;a href="https://fetchsandbox.com/webhook-sandbox" rel="noopener noreferrer"&gt;webhook sandbox&lt;/a&gt; or a test environment that lets you inspect the full Stripe flow end-to-end instead of only replaying one event at a time.&lt;/p&gt;

&lt;p&gt;The useful part is not the mock payload. It is being able to see the request, the event, and the resulting state in one place.&lt;/p&gt;

&lt;p&gt;For Stripe-specific workflow context, this is also why a runnable &lt;a href="https://fetchsandbox.com/docs/stripe" rel="noopener noreferrer"&gt;Stripe portal&lt;/a&gt; is more useful than example requests alone.&lt;/p&gt;

&lt;p&gt;Curious what other people use here. Do you mostly replay events, tunnel to localhost, or test the full PaymentIntent plus webhook path every time?&lt;/p&gt;

</description>
      <category>stripe</category>
      <category>webhooks</category>
      <category>api</category>
      <category>testing</category>
    </item>
    <item>
      <title>what actually happens when you provision a phone number in twilio</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Mon, 13 Apr 2026 06:06:55 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/what-actually-happens-when-you-provision-a-phone-number-in-twilio-11fd</link>
      <guid>https://forem.com/fetchsandbox/what-actually-happens-when-you-provision-a-phone-number-in-twilio-11fd</guid>
      <description>&lt;p&gt;i was trying to understand how twilio workflows actually behave end-to-end… not just from docs, but in practice&lt;/p&gt;

&lt;p&gt;so i ran a simple flow:&lt;br&gt;
provisioning a phone number&lt;/p&gt;

&lt;p&gt;instead of just calling the api and stopping there, i wanted to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the request looks like
&lt;/li&gt;
&lt;li&gt;what the response returns
&lt;/li&gt;
&lt;li&gt;what webhook events get triggered
&lt;/li&gt;
&lt;li&gt;what the final state looks like
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  here’s the full flow
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/UCweHhp6ZkM"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  what’s interesting
&lt;/h2&gt;

&lt;p&gt;a few things stood out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it’s not just request → response
&lt;/li&gt;
&lt;li&gt;webhook events are part of the flow
&lt;/li&gt;
&lt;li&gt;state changes matter just as much as the initial api call
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;most examples usually stop at:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“here’s the request and response”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;but in real integrations, you need to care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when webhooks fire
&lt;/li&gt;
&lt;li&gt;how many times they fire
&lt;/li&gt;
&lt;li&gt;what state the resource ends up in
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  why this matters
&lt;/h2&gt;

&lt;p&gt;if you’re building integrations, especially anything async:&lt;/p&gt;

&lt;p&gt;just checking a 200 response isn’t enough&lt;/p&gt;

&lt;p&gt;you need to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how the system behaves over time
&lt;/li&gt;
&lt;li&gt;what events are emitted
&lt;/li&gt;
&lt;li&gt;how your system reacts to them
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  curious how others are testing this
&lt;/h2&gt;

&lt;p&gt;when you integrate with twilio (or similar apis):&lt;/p&gt;

&lt;p&gt;do you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;just rely on docs + sandbox?
&lt;/li&gt;
&lt;li&gt;simulate webhooks manually?
&lt;/li&gt;
&lt;li&gt;build your own test harness?
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;would love to hear how others approach this&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>softwareengineering</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Stop mocking APIs. Use a stateful sandbox instead.</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Thu, 09 Apr 2026 12:32:56 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/stop-mocking-apis-use-a-stateful-sandbox-instead-3f6f</link>
      <guid>https://forem.com/fetchsandbox/stop-mocking-apis-use-a-stateful-sandbox-instead-3f6f</guid>
      <description>&lt;p&gt;Every developer has written this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TODO: replace with real API call&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;usr_123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then forgotten about the TODO. Then shipped it. Then found out in production that the real API returns &lt;code&gt;state&lt;/code&gt; not &lt;code&gt;status&lt;/code&gt;, and the ID format is &lt;code&gt;user_2xAbC&lt;/code&gt; not &lt;code&gt;usr_123&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mocks lie. Sandboxes don't.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What if you could test against real API behavior — without production credentials?
&lt;/h2&gt;

&lt;p&gt;That's what &lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;FetchSandbox&lt;/a&gt; does. Give it any OpenAPI spec, and it provisions a stateful sandbox in seconds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /users&lt;/code&gt; creates a user that persists&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /users/{id}&lt;/code&gt; returns it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PATCH /users/{id}&lt;/code&gt; with &lt;code&gt;{ "banned": true }&lt;/code&gt; updates the state&lt;/li&gt;
&lt;li&gt;Webhook events fire on every state change&lt;/li&gt;
&lt;li&gt;Invalid transitions return proper error codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No mock files. No Wiremock configs. No Postman collections to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  We just added 5 new APIs
&lt;/h2&gt;

&lt;p&gt;This week we onboarded five APIs developers actually struggle to test:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Endpoints&lt;/th&gt;
&lt;th&gt;Why it's hard to test&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fetchsandbox.com/docs/clerk" rel="noopener noreferrer"&gt;&lt;strong&gt;Clerk&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;196&lt;/td&gt;
&lt;td&gt;SSO flows need a real IdP, sessions expire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fetchsandbox.com/docs/resend" rel="noopener noreferrer"&gt;&lt;strong&gt;Resend&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;Actually sends emails, burns through quota&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fetchsandbox.com/docs/cohere" rel="noopener noreferrer"&gt;&lt;strong&gt;Cohere&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;API credits add up, RAG pipeline iteration is expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fetchsandbox.com/docs/svix" rel="noopener noreferrer"&gt;&lt;strong&gt;Svix&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;Webhook delivery fails to localhost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fetchsandbox.com/docs/glean" rel="noopener noreferrer"&gt;&lt;strong&gt;Glean&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;Enterprise search needs connected data sources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each comes with curated workflows — real multi-step integration flows you can run instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works in 30 seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Create a sandbox&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://fetchsandbox.com/api/sandboxes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"spec_id":"clerk"}'&lt;/span&gt;

&lt;span class="c"&gt;# Returns: { "id": "a1b2c3", "credentials": [{ "api_key": "sandbox_..." }] }&lt;/span&gt;

&lt;span class="c"&gt;# 2. Use it like the real API&lt;/span&gt;
curl https://fetchsandbox.com/sandbox/a1b2c3/users &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sandbox_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"email_address": ["dev@acme.com"], "first_name": "Jane"}'&lt;/span&gt;

&lt;span class="c"&gt;# 3. State persists — fetch the user you just created&lt;/span&gt;
curl https://fetchsandbox.com/sandbox/a1b2c3/users/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sandbox_..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Real stateful behavior, proper error codes, webhook events — all from the OpenAPI spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent-ready integration guide
&lt;/h2&gt;

&lt;p&gt;Here's the part that surprised us. We generate "integration guides" — Markdown files that contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every verified endpoint with exact request/response shapes&lt;/li&gt;
&lt;li&gt;State machine transitions (what can happen to a subscription after it's created?)&lt;/li&gt;
&lt;li&gt;Webhook events that fire at each step&lt;/li&gt;
&lt;li&gt;Sandbox credentials for testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you hand this to a coding agent (Claude Code, Cursor, Copilot), the agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads the guide&lt;/li&gt;
&lt;li&gt;Inspects your existing repo patterns&lt;/li&gt;
&lt;li&gt;Asks about scope before implementing&lt;/li&gt;
&lt;li&gt;Builds working integration code using only verified data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;No hallucinated endpoints. No invented status codes. Every fact comes from sandbox execution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We tested this with Paddle's subscription lifecycle. The agent built a complete billing dashboard — product catalog, checkout, subscription management with activate/pause/resume/cancel — from just the integration guide.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rnagulapalle/sandbox/tree/main/showcase/paddle-billing" rel="noopener noreferrer"&gt;See the code →&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  49 APIs, 9,300+ endpoints
&lt;/h2&gt;

&lt;p&gt;The full catalog includes Stripe, Paddle, PayPal, Twilio, GitHub, OpenAI, Shopify, DigitalOcean, Datadog, and 40 more.&lt;/p&gt;

&lt;p&gt;Every spec gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic docs portal&lt;/li&gt;
&lt;li&gt;Workflow discovery&lt;/li&gt;
&lt;li&gt;Stateful sandbox&lt;/li&gt;
&lt;li&gt;Integration guides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;Browse the catalog →&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;We're onboarding new APIs every week. If there's an API you're struggling to test, &lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;let us know&lt;/a&gt; — onboarding takes minutes if you have an OpenAPI spec.&lt;/p&gt;

&lt;p&gt;Next up: Deepgram, AssemblyAI, Stytch, Replicate, and Lago.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;FetchSandbox is free to try. No signup required. Sandboxes run in your browser.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>got a 200 response in minutes… still took hours to know if anything worked</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Sun, 05 Apr 2026 00:05:58 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/got-a-200-response-in-minutes-still-took-hours-to-know-if-anything-worked-4j1b</link>
      <guid>https://forem.com/fetchsandbox/got-a-200-response-in-minutes-still-took-hours-to-know-if-anything-worked-4j1b</guid>
      <description>&lt;p&gt;i was integrating with Paddle recently&lt;/p&gt;

&lt;p&gt;got a 200 response pretty quickly&lt;br&gt;&lt;br&gt;
so i thought… ok this should be straightforward&lt;/p&gt;

&lt;p&gt;it wasn’t&lt;/p&gt;

&lt;p&gt;not because the API was hard&lt;br&gt;&lt;br&gt;
but because i couldn’t tell if anything actually worked&lt;/p&gt;

&lt;h2&gt;
  
  
  what’s easy now
&lt;/h2&gt;

&lt;p&gt;most APIs today are pretty good at getting you started&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docs are cleaner
&lt;/li&gt;
&lt;li&gt;SDKs are better
&lt;/li&gt;
&lt;li&gt;you can make your first API call in minutes
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that part is honestly not the problem anymore&lt;/p&gt;

&lt;h2&gt;
  
  
  where things slow down
&lt;/h2&gt;

&lt;p&gt;the time goes after that first 200&lt;/p&gt;

&lt;p&gt;you start asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;did the webhook fire?
&lt;/li&gt;
&lt;li&gt;did it fire with the right payload?
&lt;/li&gt;
&lt;li&gt;did the state actually change?
&lt;/li&gt;
&lt;li&gt;is it the correct state or just “some state”?
&lt;/li&gt;
&lt;li&gt;what happens if this fails?
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and there’s no single place to see this&lt;/p&gt;

&lt;p&gt;so you jump between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your terminal
&lt;/li&gt;
&lt;li&gt;webhook logs
&lt;/li&gt;
&lt;li&gt;dashboards
&lt;/li&gt;
&lt;li&gt;your own code
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;trying to figure out what actually happened&lt;/p&gt;

&lt;h2&gt;
  
  
  the loop
&lt;/h2&gt;

&lt;p&gt;this is usually how it goes:&lt;/p&gt;

&lt;p&gt;call API → looks fine&lt;br&gt;&lt;br&gt;
wait for webhook → nothing&lt;br&gt;&lt;br&gt;
retry → webhook shows up twice&lt;br&gt;&lt;br&gt;
check state → not what you expected&lt;br&gt;&lt;br&gt;
add logs → try again  &lt;/p&gt;

&lt;p&gt;repeat&lt;/p&gt;

&lt;p&gt;after a few tries you’re not even sure what you’re debugging anymore&lt;/p&gt;

&lt;h2&gt;
  
  
  what we optimize for
&lt;/h2&gt;

&lt;p&gt;most DX today is focused on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;how fast can you make your first API call&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;which is useful… but not enough&lt;/p&gt;

&lt;p&gt;because calling the API is not the hard part anymore&lt;/p&gt;

&lt;h2&gt;
  
  
  what actually matters
&lt;/h2&gt;

&lt;p&gt;what matters is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;how long it takes before you trust your integration&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;not “it returned 200”&lt;/p&gt;

&lt;p&gt;but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the system behaves the way you expect
&lt;/li&gt;
&lt;li&gt;state moves correctly
&lt;/li&gt;
&lt;li&gt;webhooks fire correctly
&lt;/li&gt;
&lt;li&gt;retries don’t break things
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that’s what actually decides if you can ship&lt;/p&gt;

&lt;h2&gt;
  
  
  docs vs reality
&lt;/h2&gt;

&lt;p&gt;docs show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request
&lt;/li&gt;
&lt;li&gt;response
&lt;/li&gt;
&lt;li&gt;“success”
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;real life looks more like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request succeeds
&lt;/li&gt;
&lt;li&gt;webhook missing
&lt;/li&gt;
&lt;li&gt;state is off
&lt;/li&gt;
&lt;li&gt;retry changes something else
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and now you’re debugging across multiple places&lt;/p&gt;

&lt;h2&gt;
  
  
  what we started doing
&lt;/h2&gt;

&lt;p&gt;after hitting this a few times, we stopped looking at just API calls&lt;/p&gt;

&lt;p&gt;and started looking at the whole flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create → update → confirm
&lt;/li&gt;
&lt;li&gt;state transitions
&lt;/li&gt;
&lt;li&gt;webhook events
&lt;/li&gt;
&lt;li&gt;final result
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;in one place&lt;/p&gt;

&lt;p&gt;not just:&lt;/p&gt;

&lt;p&gt;“did it return 200”&lt;/p&gt;

&lt;p&gt;but:&lt;/p&gt;

&lt;p&gt;“did it actually work”&lt;/p&gt;

&lt;p&gt;ended up building a small internal tool to run these flows end-to-end and see the final state instead of stitching logs manually&lt;/p&gt;

&lt;p&gt;turned it into something usable here: &lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;https://fetchsandbox.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  still feels like a gap
&lt;/h2&gt;

&lt;p&gt;curious how others deal with this&lt;/p&gt;

&lt;p&gt;are teams actually measuring this properly?&lt;/p&gt;

&lt;p&gt;or are we all still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reading logs
&lt;/li&gt;
&lt;li&gt;retrying requests
&lt;/li&gt;
&lt;li&gt;and hoping it works in prod&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>devrel</category>
      <category>webdev</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Our partners kept breaking on staging. So we gave them production access. (Don't do this.)</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Wed, 01 Apr 2026 23:00:29 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/our-partners-kept-breaking-on-staging-so-we-gave-them-production-access-dont-do-this-1nna</link>
      <guid>https://forem.com/fetchsandbox/our-partners-kept-breaking-on-staging-so-we-gave-them-production-access-dont-do-this-1nna</guid>
      <description>&lt;p&gt;I work on an embed platform. Partners integrate our APIs to build features into their own products — payments, identity verification, onboarding flows.&lt;/p&gt;

&lt;p&gt;We spent six months trying to answer one question: &lt;strong&gt;how do partners test their integration before going live?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We tried four approaches. Each one failed in a new and exciting way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 1: "Just use our docs"
&lt;/h2&gt;

&lt;p&gt;We pointed partners to our API docs. Endpoints, auth, request/response examples. "You're good to go."&lt;/p&gt;

&lt;p&gt;Partners would start building, hit something we didn't document, open a support ticket, wait two days, lose momentum. A few never came back. One told us they went with a competitor because "we couldn't get a working test setup in a week."&lt;/p&gt;

&lt;p&gt;The docs described what &lt;em&gt;should&lt;/em&gt; happen. Partners had no way to see what &lt;em&gt;actually&lt;/em&gt; happens until they wrote code, deployed it, and hoped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 2: Internal UAT box
&lt;/h2&gt;

&lt;p&gt;Next idea: give partners access to our internal UAT environment. IP whitelisted, shared credentials. We told them "please don't break anything" without a hint of irony.&lt;/p&gt;

&lt;p&gt;Worked for three weeks.&lt;/p&gt;

&lt;p&gt;Then QA pushed a bad config on a Tuesday afternoon. Partner's integration broke. They didn't know it was our side — spent two days debugging their own code before opening a ticket. By the time we figured it out, their engineering lead was cc'ing our VP.&lt;/p&gt;

&lt;p&gt;Around the same time, another team was running load tests on UAT. Partner's API calls were timing out. Their CTO emailed us asking if our platform was "production-ready."&lt;/p&gt;

&lt;p&gt;UAT was built for us to break things. We invited external partners into that mess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 3: Staging with IP whitelisting
&lt;/h2&gt;

&lt;p&gt;We carved out a "partner staging" — same codebase, separate deployment, IP whitelisted per partner. This felt like the grown-up solution.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IP whitelists were a full-time job.&lt;/strong&gt; Every new partner meant new firewall rules. Partners working from home had different IPs than their office. One partner's CTO was traveling and couldn't hit the sandbox from his hotel. We were debugging VPN configs at 11pm on a Thursday.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test data went stale.&lt;/strong&gt; Partners tried to create orders for products that existed in production but not in staging. "Your API returns 404 for product_id XYZ." Yes, because nobody seeded staging in three weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploys kept colliding.&lt;/strong&gt; Engineers would ship to staging without checking if partners were actively testing. A deploy during a partner's live demo call is the kind of thing that gets brought up in quarterly business reviews.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost.&lt;/strong&gt; Running a full mirror of production for partner testing. Database, compute, monitoring, on-call rotation. All for an environment that got used maybe 10 hours a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt 4: Production access
&lt;/h2&gt;

&lt;p&gt;I wish I was kidding.&lt;/p&gt;

&lt;p&gt;The reasoning: "staging is flaky, UAT is a disaster, let's just whitelist them on production with read-only test accounts."&lt;/p&gt;

&lt;p&gt;The API was stable! Partners were happy. For about a month.&lt;/p&gt;

&lt;p&gt;Then one partner's integration bug created 400 orphaned records in production. Our data team spent a weekend cleaning it up.&lt;/p&gt;

&lt;p&gt;Compliance wanted to know why test payloads with fake PII were hitting the production database.&lt;/p&gt;

&lt;p&gt;When we needed to do maintenance, we had to coordinate downtime with partners who were "just running a few test calls."&lt;/p&gt;

&lt;p&gt;We'd built the world's most expensive and dangerous sandbox: production with IP whitelisting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually needed
&lt;/h2&gt;

&lt;p&gt;After burning six months on this, the answer felt obvious in hindsight. Partners didn't need access to &lt;em&gt;our&lt;/em&gt; infrastructure at all.&lt;/p&gt;

&lt;p&gt;They needed an API that looked and acted like ours — same endpoints, same schemas, same auth patterns — but was completely separate. Something where POST actually creates a resource, GET retrieves it, state transitions work, and nobody else's deploy can break it.&lt;/p&gt;

&lt;p&gt;Not a mock server (those are stateless — step 2 doesn't know about step 1). Not a shared staging box (those are everybody's problem). A dedicated, isolated sandbox generated from the same OpenAPI spec our real API uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm building now
&lt;/h2&gt;

&lt;p&gt;This experience is half the reason I started working on &lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;FetchSandbox&lt;/a&gt;. You give it an OpenAPI spec and it spins up a stateful sandbox — real CRUD, state machines, webhook events, seed data. No infrastructure to manage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Partner gets a sandbox from your spec&lt;/span&gt;
npx fetchsandbox generate ./your-api-openapi.yaml

&lt;span class="c"&gt;# They can call endpoints immediately&lt;/span&gt;
curl https://your-api.fetchsandbox.com/v1/orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"api-key: sandbox_abc123"&lt;/span&gt;
&lt;span class="c"&gt;# → 200 OK, returns realistic seed data&lt;/span&gt;

&lt;span class="c"&gt;# They can run the full integration workflow&lt;/span&gt;
fetchsandbox run your-api create-and-fulfill-order
&lt;span class="c"&gt;# → ✓ Create order — 201&lt;/span&gt;
&lt;span class="c"&gt;# → ✓ Add line items — 200&lt;/span&gt;
&lt;span class="c"&gt;# → ✓ Submit for fulfillment — 200&lt;/span&gt;
&lt;span class="c"&gt;# → ✓ Webhook: order.fulfilled fired&lt;/span&gt;
&lt;span class="c"&gt;# → All steps passed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No staging environment to maintain. No IP whitelists. No risk to production. Partners get their own URL, their own data, their own credentials. Your team doesn't touch it.&lt;/p&gt;

&lt;p&gt;When a partner asks "does this workflow work?" — they prove it themselves. No support ticket needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The time sink nobody tracks
&lt;/h2&gt;

&lt;p&gt;If you run a partner API, add up how many hours your team spends per month on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provisioning and maintaining test environments&lt;/li&gt;
&lt;li&gt;Debugging "is your sandbox down?" tickets&lt;/li&gt;
&lt;li&gt;Managing IP whitelists and VPN access&lt;/li&gt;
&lt;li&gt;Re-seeding stale test data&lt;/li&gt;
&lt;li&gt;Coordinating deploys around partner testing schedules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At my company it was easily 20-30 hours a month across engineering and DevOps. For a problem that shouldn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;FetchSandbox&lt;/a&gt; works with any OpenAPI 3.x spec. 19 APIs are live — Stripe, GitHub, Twilio, WorkOS, and more. No signup.&lt;/p&gt;

&lt;p&gt;If you run an API platform and want to try this with your own spec, reach out — &lt;a href="https://x.com/fetchsandbox" rel="noopener noreferrer"&gt;@fetchsandbox&lt;/a&gt; on X or &lt;a href="mailto:hello@fetchsandbox.com"&gt;hello@fetchsandbox.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Free during early access.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I know I'm not the only one who's been through this. UAT → staging → "just give them prod." If you've found a better way to handle partner sandbox environments, I'd genuinely like to know. Still figuring this out.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>apitesting</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>This is amazing truly</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Tue, 31 Mar 2026 00:53:27 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/this-is-amazing-truly-2agp</link>
      <guid>https://forem.com/fetchsandbox/this-is-amazing-truly-2agp</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0" class="crayons-story__hidden-navigation-link"&gt;Stop Paying for Cloud: Deploy a Highly Available Cluster on Your Own Hardware in Minutes | MicroCloud&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/QL5K-hEcdOE" title="Stop Paying for Cloud: Deploy a Highly Available Cluster on Your Own Hardware in Minutes | MicroCloud"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/axrisi" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3226798%2F0c0a8594-658c-4146-a639-8068ede85f67.jpg" alt="axrisi profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/axrisi" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nikoloz Turazashvili (@axrisi)
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nikoloz Turazashvili (&lt;a class="mentioned-user" href="https://dev.to/axrisi"&gt;@axrisi&lt;/a&gt;)
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3409402" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/axrisi" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3226798%2F0c0a8594-658c-4146-a639-8068ede85f67.jpg" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nikoloz Turazashvili (@axrisi)&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 26&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0" id="article-link-3409402"&gt;
          Stop Paying for Cloud: Deploy a Highly Available Cluster on Your Own Hardware in Minutes | MicroCloud
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/linux"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;linux&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloud"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloud&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;29&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/axrisi/stop-paying-for-cloud-deploy-a-highly-available-cluster-on-your-own-hardware-in-minutes-14f0#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              3&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>linux</category>
      <category>devops</category>
      <category>cloud</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>My mock server lied to me. So I built a stateful API sandbox.</title>
      <dc:creator>FetchSandbox</dc:creator>
      <pubDate>Mon, 30 Mar 2026 23:26:50 +0000</pubDate>
      <link>https://forem.com/fetchsandbox/my-mock-server-lied-to-me-so-i-built-a-stateful-api-sandbox-549n</link>
      <guid>https://forem.com/fetchsandbox/my-mock-server-lied-to-me-so-i-built-a-stateful-api-sandbox-549n</guid>
      <description>&lt;p&gt;Last month I was integrating with a payment API. Wrote my tests against a mock server, everything passed, shipped to staging — and the whole flow broke.&lt;/p&gt;

&lt;p&gt;The mock told me &lt;code&gt;POST /charges&lt;/code&gt; returns &lt;code&gt;{"id": "ch_123"}&lt;/code&gt;. And it does. But my code then called &lt;code&gt;GET /charges/ch_123&lt;/code&gt; to verify the status, and the mock returned 404. Because the mock doesn't actually &lt;em&gt;store&lt;/em&gt; anything. Every request lives in its own universe.&lt;/p&gt;

&lt;p&gt;I lost half a day to this. And it wasn't the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with stateless mocks
&lt;/h2&gt;

&lt;p&gt;I've used Prism, WireMock, Mockoon — they're solid tools. You point them at an OpenAPI spec and they generate responses. But the responses are canned. There's no memory between requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;POST /customers → 201 &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"cust_123"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
GET /customers/cust_123 → 404   &lt;span class="c"&gt;# has no idea you just created this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works fine for unit tests where you're testing your HTTP client. It falls apart the moment you have a multi-step flow.&lt;/p&gt;

&lt;p&gt;Think about how a real Stripe integration works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a customer&lt;/li&gt;
&lt;li&gt;Create a payment intent &lt;em&gt;for that customer&lt;/em&gt; (needs the customer ID from step 1)&lt;/li&gt;
&lt;li&gt;Confirm the payment intent (needs the PI ID from step 2)&lt;/li&gt;
&lt;li&gt;A webhook fires (your server needs to handle it)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A mock server can't do steps 2-4. The IDs don't carry over. The webhook never fires. You're testing a fantasy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually needed
&lt;/h2&gt;

&lt;p&gt;I needed a sandbox where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POST creates a real resource I can GET later&lt;/li&gt;
&lt;li&gt;IDs chain between requests like they would in production&lt;/li&gt;
&lt;li&gt;State transitions work (a charge goes from &lt;code&gt;pending&lt;/code&gt; to &lt;code&gt;succeeded&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Webhooks fire when things change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically — not a mock, but a tiny fake version of the actual API that behaves like the real thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I built one
&lt;/h2&gt;

&lt;p&gt;I've been heads-down on &lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;FetchSandbox&lt;/a&gt; for a few months now. You give it an OpenAPI spec and it generates a stateful sandbox with seed data, state machines, and webhook events.&lt;/p&gt;

&lt;p&gt;Here's what it looks like from the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; fetchsandbox

fetchsandbox generate ./stripe-openapi.yaml
&lt;span class="c"&gt;# ✓ Sandbox ready: 587 endpoints, 63 seed records&lt;/span&gt;

fetchsandbox run stripe &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;span class="c"&gt;# ✓ Accept a payment — 3/3 steps passed&lt;/span&gt;
&lt;span class="c"&gt;# ✓ Onboard a connected account — 3/3 steps passed&lt;/span&gt;
&lt;span class="c"&gt;# ✓ Respond to a dispute — 2/2 steps passed&lt;/span&gt;
&lt;span class="c"&gt;# ✓ All workflows passed — 3/3 (9ms)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;run --all&lt;/code&gt; command is the thing I wish I'd had. It executes every integration workflow end-to-end — creating resources, chaining IDs between steps, and verifying each response. If something breaks, you see exactly which step failed and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stuff that surprised me while building it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error scenarios were harder than happy paths.&lt;/strong&gt; I added a &lt;code&gt;--scenario&lt;/code&gt; flag so you can switch the whole sandbox to "auth_failure" mode and see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fetchsandbox run stripe accept_payment &lt;span class="nt"&gt;--scenario&lt;/span&gt; auth_failure
&lt;span class="c"&gt;# ✗ Step 1: POST /v1/payment_intents → 401 Unauthorized&lt;/span&gt;
&lt;span class="c"&gt;# Scenario "auth_failure" correctly caused failure.&lt;/span&gt;
&lt;span class="c"&gt;# Scenario reset to default.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My code had a bug where it didn't handle 401 on the payment intent endpoint — only on the customer endpoint. Would never have caught that with a regular mock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Webhooks were a rabbit hole.&lt;/strong&gt; In a real Stripe integration, half the logic is in webhook handlers. The sandbox now fires webhook events when resources mutate, and you can watch them in real-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fetchsandbox webhook-listen stripe
&lt;span class="c"&gt;# 12:04:31  payment_intent.created  pi_xyz  → requires_confirmation&lt;/span&gt;
&lt;span class="c"&gt;# 12:04:32  payment_intent.succeeded pi_xyz → succeeded&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Inspecting state is underrated.&lt;/strong&gt; After running a workflow, you can see exactly what's in the sandbox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fetchsandbox state stripe customers
&lt;span class="c"&gt;# customers — 3 records&lt;/span&gt;
&lt;span class="c"&gt;# ┌──────────────┬─────────────────┬──────────┐&lt;/span&gt;
&lt;span class="c"&gt;# │ id           │ email           │ status   │&lt;/span&gt;
&lt;span class="c"&gt;# ├──────────────┼─────────────────┼──────────┤&lt;/span&gt;
&lt;span class="c"&gt;# │ cust_abc123  │ test@acme.com   │ active   │&lt;/span&gt;
&lt;span class="c"&gt;# └──────────────┴─────────────────┴──────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it compares to the alternatives
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend FetchSandbox replaces everything. Here's where I honestly think it sits:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Mock server (Prism)&lt;/th&gt;
&lt;th&gt;Vendor sandbox (Stripe test mode)&lt;/th&gt;
&lt;th&gt;FetchSandbox&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;td&gt;15-30 min (account + keys)&lt;/td&gt;
&lt;td&gt;&amp;lt; 30 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stateful&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signup required&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works offline&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Hosted (offline coming)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matches prod exactly&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (schema-accurate, not logic-accurate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhooks&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (with CLI forwarding)&lt;/td&gt;
&lt;td&gt;Yes (built-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any OpenAPI spec&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Only their API&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest gap: FetchSandbox doesn't replicate vendor-specific business logic. Stripe's test mode knows that a card ending in 4242 succeeds and 4000000000000002 declines. FetchSandbox doesn't. It validates your &lt;em&gt;integration pattern&lt;/em&gt;, not the vendor's edge cases.&lt;/p&gt;

&lt;p&gt;For me, that's the right tradeoff. I use FetchSandbox while building the integration, then switch to the vendor's test mode for final validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD is where it clicks
&lt;/h2&gt;

&lt;p&gt;The thing I'm most excited about is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prove integration works before deploy&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx fetchsandbox run stripe --all --json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 0 = all workflows pass. Exit code 1 = something broke. Your pipeline catches integration regressions before they hit staging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;p&gt;I ran a benchmark — time from "I want to explore this API" to "I made my first successful call":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vendor docs path:&lt;/strong&gt; 15-30 minutes (signup → dashboard → keys → local server → SDK → code → run)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FetchSandbox path:&lt;/strong&gt; under 1 second (open portal → endpoint is callable)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://nordicapis.com/why-time-to-first-call-is-a-vital-api-metric/" rel="noopener noreferrer"&gt;Nordic APIs&lt;/a&gt; defines TTFC (time to first call) benchmarks: under 2 minutes is "Champion" tier. Over 10 minutes is a "Red Flag."&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;19 APIs are live right now — Stripe, GitHub, Twilio, WorkOS, OpenAI, DigitalOcean, and more. No signup needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fetchsandbox.com" rel="noopener noreferrer"&gt;fetchsandbox.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's free during early access while I figure out what developers actually need from it. If you try it and something breaks or feels wrong, I genuinely want to know — I'm &lt;a href="https://x.com/fetchsandbox" rel="noopener noreferrer"&gt;@fetchsandbox&lt;/a&gt; on X.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Curious what other people's testing setups look like for third-party APIs. Do you mock everything? Use vendor test modes? Some hybrid? Drop a comment — I've been deep in this problem for months and I'm still learning.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>apitesting</category>
      <category>openapi</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
