<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Stripe Dot Dev Blog]]></title><description><![CDATA[Learn how to build web and mobile applications to accept payments, send payouts, and manage your business online with Stripe.]]></description><link>https://stripe.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 16 Oct 2026 18:18:32 GMT</lastBuildDate><atom:link href="https://stripe.dev/blog/feed" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><item><title><![CDATA[Run recurring jobs on Stripe without a cron server]]></title><description><![CDATA[Stripe Workflows already could react to events and run on demand. Now, it can start a workflow on its own at a time you specify.]]></description><link>https://stripe.dev/blog/run-recurring-jobs-on-stripe-workflows</link><guid isPermaLink="true">https://stripe.dev/blog/run-recurring-jobs-on-stripe-workflows</guid><dc:creator><![CDATA[Tanya Boiteau]]></dc:creator><pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/run-recurring-jobs-on-stripe-workflows/image2.png"/><link rel="preload" as="image" href="https://stripe.dev/images/run-recurring-jobs-on-stripe-workflows/image3.png"/><link rel="preload" as="image" href="https://stripe.dev/images/run-recurring-jobs-on-stripe-workflows/image1.png"/><p>When a customer&#x27;s renewal payment fails, their subscription moves to past due while Stripe retries the payment. Retries recover some of these payments on their own, but your collections team usually wants to contact the others before the retries run out. So every weekday at 9:00 AM, someone on your finance team opens the Stripe Dashboard, filters for past-due subscriptions, and copies each one into a Slack channel so the team knows who to contact. On a quiet day, it&#x27;s a small chore. On a busy day, it doesn&#x27;t happen at all.</p>
<p>Nobody notices the skipped day until the retries run out, and a customer who would have updated their card after one friendly message has churned instead. The check itself is predictable: the same list, the same filters, and the same message, every morning. What&#x27;s unpredictable is whether a person remembers to run it.</p>
<p>This pattern shows up across finance and operations teams. Daily balance checks, weekly payout reports, and month-end reconciliation all depend on the calendar, not on something happening in your account. <a href="https://docs.stripe.com/workflows">Stripe Workflows</a> already could react to events and run on demand. Now, it can start a workflow on its own at a time you specify.</p>
<p>The usual fix: a cron job</p>
<p>Teams that want to automate a recurring check usually end up writing a scheduled job. A typical version runs on your own infrastructure, lists past-due subscriptions through the Stripe API, and posts each one to Slack through an incoming webhook:</p>
<pre><code class="language-js">// past-due-subscriptions.js, run by cron on a UTC server: 0 16 * * 1-5
const stripe = require(&#x27;stripe&#x27;)(process.env.STRIPE_SECRET_KEY);

async function main() {
  for await (const subscription of stripe.subscriptions.list({
    status: &#x27;past_due&#x27;,
    expand: [&#x27;data.customer&#x27;],
    limit: 100,
  })) {
    await fetch(process.env.SLACK_WEBHOOK_URL, {
      method: &#x27;POST&#x27;,
      headers: { &#x27;Content-Type&#x27;: &#x27;application/json&#x27; },
      body: JSON.stringify({
        text: `Subscription for ${subscription.customer.email} is past due: https://dashboard.stripe.com/subscriptions/${subscription.id}`,
      }),
    });
  }
}

main().catch((err) =&gt; {
  console.error(err);
  process.exit(1);
});
</code></pre>
<p>The script is short, but the system around it isn&#x27;t. You need a server or a scheduled function to run it, a place to store a Stripe secret key and a Slack webhook URL, and a deploy process for every change to the message. Time zones are your problem too. A cron expression on a UTC server doesn&#x27;t follow daylight saving time, so a job set for 9:00 AM in Los Angeles runs at 8:00 AM local time for part of the year. If the job fails, it often fails silently. You find out when someone asks why the channel has been quiet for a week.</p>
<p>The script also lives outside Stripe, and usually outside the finance team&#x27;s reach. If the collections lead wants to change the channel, add the customer&#x27;s name to the message, or check twice a day instead of once, they file a ticket with engineering. A 9:00 AM check that should belong to the team that relies on it ends up depending on someone else&#x27;s sprint.</p>
<h2>Introducing recurring schedule triggers in Stripe Workflows</h2>
<p>Stripe Workflows now supports <a href="https://docs.stripe.com/workflows/triggers#recurring-schedule-triggers">recurring schedule triggers</a>. You choose when a workflow runs, such as every weekday at 9:00 AM, every other Monday, or the last day of each month, and Stripe starts a run at each scheduled time. There&#x27;s no server to run, no cron expression to maintain, and no API key to store.</p>
<p>A recurring schedule is one of three trigger types that Stripe Workflows supports:</p>
<ul>
<li>Event triggers run when something happens in your account, such as a payment succeeding or a dispute being created.</li>
<li>Recurring schedule triggers run at the times you set.</li>
<li>On-demand triggers run when you start them from the Dashboard or call the API.</li>
</ul>
<p>Scheduled workflows pair well with list actions and loops. A common pattern is to schedule the workflow, retrieve a list of objects, and loop over that list to act on each item, which is the shape of the past-due subscription check.</p>
<p>Because the workflow lives in the Dashboard, the team that owns the process can read it, change it, and see every run.</p>
<h2>Walkthrough: notify your team about past-due subscriptions</h2>
<p>The past-due subscription check uses a recurring schedule trigger, a list action, and a loop. Inside the loop, the workflow looks up the customer, skips subscriptions the team already knows about, sends a Slack notification, and records the notification in the subscription&#x27;s metadata.</p>
<p><img src="https://stripe.dev/images/run-recurring-jobs-on-stripe-workflows/image2.png" alt="A recurring schedule workflow diagram"/></p>
<ol>
<li>Set the schedule</li>
</ol>
<p>In the workflow builder, click Add a trigger, then select Tools, where recurring schedule and on-demand triggers live, and choose Recurring schedule. Set Repeat every to 1, select Week, and select Monday through Friday under Days of the week. Set Starting to the date and time of the first run, and choose a Time zone, such as America - Los Angeles. The workflow runs at the time in Starting on each scheduled day, beginning with the first scheduled time on or after it. After you click Done, the trigger shows a summary so you can confirm the schedule before you publish.</p>
<p><img src="https://stripe.dev/images/run-recurring-jobs-on-stripe-workflows/image3.png" alt="Setting the recurring schedule"/></p>
<p>Schedules can run as often as every hour or as rarely as once a year. They follow the local clock, so a 9:00 AM workflow runs at 9:00 AM all year, and the Triggers docs cover the hour when clocks change.</p>
<ol start="2">
<li>List the subscriptions</li>
</ol>
<p>Add a List subscriptions action and set the status filter to <code>past_due</code>. A subscription stays past due until a payment succeeds or the retries run out, so the same subscription shows up every morning until it&#x27;s resolved.</p>
<ol start="3">
<li>Look up the customer</li>
</ol>
<p>List subscriptions returns the customer&#x27;s ID, not their email, so add a Retrieve a customer action and set its customer ID to the current subscription&#x27;s customer. Because this action references a field from the list results, the builder adds a For each item in the Subscriptions loop, and every step after it runs once per subscription.</p>
<ol start="4">
<li>Skip subscriptions the team already knows about</li>
</ol>
<p>Because past-due subscriptions show up every morning, the workflow needs to remember which ones it already reported. It does that with a metadata key, <code>collections_notified_invoice</code>, that the last step in the loop writes after the Slack message goes out. You don&#x27;t create the key ahead of time, so on the first run, it&#x27;s empty for every subscription.</p>
<p>Inside the loop, add a condition. For the field, choose Subscription, then Metadata, and enter <code>collections_notified_invoice</code> as the key. Set the operator to isn&#x27;t equal to, and for the value, insert Subscription | Latest invoice ID. Put the next two steps on the If this condition is met branch.</p>
<p><img src="https://stripe.dev/images/run-recurring-jobs-on-stripe-workflows/image1.png" alt="Adding a condition"/></p>
<p>On the first run, the empty key isn&#x27;t equal to the latest invoice ID, so the team gets notified. The next morning, the key holds that invoice ID, so the loop skips the subscription. If the subscription later goes past due on a new invoice, the IDs differ again and the team hears about it. A yes-or-no flag wouldn&#x27;t work, because it stays set after the first notification and hides every later one.</p>
<ol start="5">
<li>Notify the team</li>
</ol>
<p>On that branch, add another action, click Apps, and under Slack, choose Send Slack Notification. If you haven&#x27;t installed the Slack app, the builder prompts you to install it from the <a href="https://marketplace.stripe.com/apps/slack-workflows">Stripe App Marketplace</a> and connect your Slack workspace. Choose a channel, such as #collections, write a message template that includes the customer&#x27;s email and the subscription ID, and add a Dashboard link to the subscription so the team can open it from Slack.</p>
<ol start="6">
<li>Record the notification</li>
</ol>
<p>Finally, on the same branch, add an Update subscription action that sets <code>collections_notified_invoice</code> to the subscription&#x27;s latest invoice ID. Anyone who opens the subscription can then see that the collections team has already been notified.</p>
<p>The loop handles pagination for you. If an action fails for one subscription, the loop stops there, and run details show which subscriptions were processed. Any subscription the loop didn’t reach gets picked up on the next run. If the Slack notification succeeds but the metadata update fails, the workflow might notify the team about that subscription again. That’s why the Slack step comes first - a duplicate message is better than a missed one. Each action inside a loop counts once per item. Retrieve a customer runs for every past-due subscription, but the condition limits the Slack and update steps to the ones that are new.</p>
<h2>Monitor and manage the schedule</h2>
<p>After you publish, the workflow&#x27;s page shows Next run, and Recent runs lists every run with its status. Click a run to see each step&#x27;s input and output and, for a failed run, which step failed and why. To get alerted, create a second workflow triggered by the Workflow run failed event. Search for it in the trigger picker, or find it under More &gt; Advanced &gt; Workflow run. Then have it send a Slack notification to a different channel or Email a team member. To stop on a date, set Ending under Advanced. To pause, deactivate the workflow. Times missed while it&#x27;s inactive are skipped.</p>
<p>A scheduled workflow runs only on its schedule, so you can&#x27;t start it by hand. To test it, build it in a sandbox, set Starting a few minutes ahead, and check Recent runs. To run the same check on demand, create a separate workflow with an on-demand trigger.</p>
<h2>What changes for your team</h2>
<p>Recurring schedule triggers move calendar-based work into the same place as the rest of your Stripe automation. The past-due subscription check runs every weekday at 9:00 AM local time whether or not anyone remembers it, each past-due subscription reaches the collections team once, and the subscription itself records that the team was notified.</p>
<p>There&#x27;s no server, secret key, or daylight saving time logic to maintain. The team that relies on the check can change the channel, the message, or the schedule themselves, and when something goes wrong, the run shows which step failed and why.</p>
<p>Recurring schedule triggers are available in Workflows today. To build your first scheduled workflow, go to Workflows in the Dashboard.</p>]]></content:encoded></item><item><title><![CDATA[Fine-tuning LLMs for risk assessment]]></title><description><![CDATA[SFT helps Stripes make more precise risk assessments. We explain the training approach and investigate what the model learns about legitimate businesses.]]></description><link>https://stripe.dev/blog/training-llms-for-risk-assessment</link><guid isPermaLink="true">https://stripe.dev/blog/training-llms-for-risk-assessment</guid><dc:creator><![CDATA[Hangzhi Guo]]></dc:creator><pubDate>Fri, 09 Oct 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/training-llms-for-risk-assessment/image2.gif"/><link rel="preload" as="image" href="https://stripe.dev/images/training-llms-for-risk-assessment/image4.gif"/><link rel="preload" as="image" href="https://stripe.dev/images/training-llms-for-risk-assessment/image1.png"/><link rel="preload" as="image" href="https://stripe.dev/images/training-llms-for-risk-assessment/image3.png"/><blockquote>
<p>This is the first of two posts on fine-tuning LLMs for risk assessment. Here, we focus on learning from labels; the second post will explore learning from explanations.</p>
</blockquote>
<h2>High-stakes risk assessment</h2>
<p>Stripe powers millions of businesses moving trillions of dollars annually while operating self-serve, globally adopted, financial infrastructure. To achieve this safely, we make thousands of risk decisions every single second. Is this transaction legitimate? Is the business trustworthy? Is this product set allowed by this financial partner? Has this business become suddenly popular, or is it under attack? Should we adjust a business’ credit reserves?</p>
<p>A false negative lets through fraud and abuse, reducing trust and making the internet economy less safe . A false positive can stymie legitimate economic activity and meaningfully affect a business’ livelihood. Balancing these risks, in near real-time at high precision, requires models to connect patterns in our data with an understanding of how the business operates in the wider economy.</p>
<p>Several properties of LLMs make them well suited to these risk decisions. LLMs bring world knowledge from pretraining that helps interpret Stripe’s internal data in the context of real-world business practices. LLMs also provide a flexible way to integrate heterogeneous features, including numerical, categorical, text, and time-series data. Combining these inputs in conventional tabular or sequence models often requires feature engineering, type-specific encodings, or specialized architectures. With LLMs, we can serialize these features into a shared text representation and process them with a single model, enabling it to reason jointly over different feature types.</p>
<figure>
  <img src="https://stripe.dev/images/training-llms-for-risk-assessment/image2.gif" alt="Serializing business features into LLM input"/>
  <figcaption><strong>Figure 1.</strong> Serializing business features into LLM input. A feature serializer converts heterogeneous features into a text-based business record that an LLM can reason over to assess risk.</figcaption>
</figure>
<p>The problem is that LLMs are word prediction machines, and are not designed for the reliably rigorous statistical analysis Stripe needs as inputs to our risk decisions.</p>
<p>We confirmed this intuition in early testing by asking a model to assess the likelihood that a business was fraudulent. We compared zero-shot prompting, which relied solely on the model’s pretraining, with domain-guided prompting where we made Stripe’s review criteria explicit, including which signals to consider and how to interpret them in context.</p>
<p>For each business, we collected the features used in human reviews, including card payments, money movement, and account activity, and serialized them into a text-based account record (see Figure 1). The model produced a verbalized probability of fraud, which we used as the risk score (<a href="https://aclanthology.org/2023.emnlp-main.330/">Tian et al. 2023</a>). This continuous score allowed us to rank accounts and set thresholds for routing them to human reviewers, balancing fraud detection against disruption to legitimate businesses.</p>
<p>The LLMs performed poorly against human review. While domain-guided prompting outperforms zero-shot, it fails to reach the human reviewer’s precision at the same recall. Both model predictions and initial human-review decisions are evaluated against labels that incorporate subsequent evidence and outcomes. It’s worth noting that for these results, and the others shared below, that fraud prevalence and the mix of cases affect precision, so results on accounts selected for review should not be read as performance across all Stripe businesses.</p>





















<table><thead><tr><th><strong>Method</strong></th><th><strong>Precision @ Human-Review Recall</strong></th></tr></thead><tbody><tr><td>Zero-shot</td><td>0.487</td></tr><tr><td>Domain-guided prompting</td><td>0.706</td></tr><tr><td>Human review</td><td>0.790</td></tr></tbody></table>
<p>So how can we get a word predictor to natively conduct and output high quality statistical analysis?</p>
<h2>Supervised fine-tuning (SFT)</h2>
<p>To fit an LLM into this analytical structure, we explored labels-only supervised fine-tuning (SFT) of an open-weight 4B-parameter LLM. Unlike prompting, SFT updates the model’s weights to predict each account’s review label from its serialized record. Let $$x_i$$ be the state of the business’s account $$i$$, $$s(x_i)$$ its text serialization, and $$y_i \in {0,1}$$ the review label, where $$y_i = 1$$ denotes fraud. The training dataset is $$\mathcal{D} = {(s(x_i), y_i)}_i$$. The target response contains only the label $$y_i$$, so the objective reduces to the negative log-likelihood of that label:</p>
<p>$$
\theta^* = \underset{\theta}{\arg\min}; \mathbb{E}<em>{(s(x_i), y_i) \sim \mathcal{D}} \left[-\log p</em>{\theta}!\left(y_i \mid s(x_i)\right)\right]
$$</p>
<p>Although the model is trained to output a discrete label, we can derive a continuous risk score from its next-token distribution. For single-token class labels, we normalize the probabilities of the fraud and non-fraud tokens at the answer position. If $$\ell_1$$ and $$\ell_0$$ are the log probabilities of the fraud and non-fraud label tokens, the fraud score is</p>
<p>$$
r(x) = \frac{e^{\ell_1}}{e^{\ell_1} + e^{\ell_0}}
$$</p>
<p>This score lets us rank accounts and choose a decision threshold. Unlike the prompted baseline, which generates a numerical risk estimate as text, this approach derives the score directly from the label-token probabilities.</p>
<p>For example, suppose fraud is encoded as yes and non-fraud as no, each represented by a single token. If the model assigns probability 0.18 to <code>yes</code>, 0.72 to <code>no</code>, and 0.10 to all other tokens, normalizing over the two valid labels yields a fraud score of 0.18 / (0.18 + 0.72) = 0.20 (see Figure 2 for illustration). Rather than retaining only the predicted label, we use the relative probability assigned to each class to obtain a continuous score.</p>
<figure>
  <img src="https://stripe.dev/images/training-llms-for-risk-assessment/image4.gif" alt="Computing a fraud score from the next-token distribution"/>
  <figcaption><strong>Figure 2.</strong> Illustration of computing a fraud score from the next-token distribution.</figcaption>
</figure>
<p>And how does this adapted model perform relative to our human reviewers?</p>
<figure>
  <img src="https://stripe.dev/images/training-llms-for-risk-assessment/image1.png" alt="Precision-recall curves on a selected fraud-review cohort"/>
  <figcaption><strong>Figure 3.</strong> Precision-recall curves on a selected fraud-review cohort.</figcaption>
</figure>
<p>The fine-tuned 4B-parameter model achieved higher precision against historical review labels, achieving approximately 24% higher average precision than domain-guided prompting and 70% higher than zero-shot prompting. These results suggest that Stripe’s risk distribution differs from the broader pretraining distribution. Injecting domain knowledge through context helps bridge that gap, but fine-tuning on our internal data captures nuances that cannot be exhaustively conveyed in written guidelines.</p>
<h3>Does SFT learn new knowledge?</h3>
<p>The gains in decision accuracy alone do not reveal whether SFT changes how the model represents risk signals, such as suspicious payment patterns or legitimate business activity. To investigate this, we compared the internal representations of this open-weight 4B with and without training on risk review data. We used linear probing (<a href="https://arxiv.org/abs/1610.01644">Alain and Bengio, 2016</a>) to measure how risk-relevant concepts could be linearly decoded from each model’s hidden representations. For each model, we extracted the final layer’s hidden state at the answer-prefill position, then trained logistic regression classifiers on these frozen representations.</p>
<p>We probed for 44 concepts using 34 binary and 10 multiclass classification targets, including risk and benign signals, account metadata, business size, case types, and evidence sufficiency. Probe labels came either from the source dataset or from the teacher-model annotations. The benign concepts capture characteristics associated with legitimate businesses, such as a consistent business description, a credible online presence, or an established processing history. Their absence isn&#x27;t a risk signal; it only means that evidence isn&#x27;t there. Risk signals are the fraud patterns reviewers flag during business review.</p>
<figure>
  <img src="https://stripe.dev/images/training-llms-for-risk-assessment/image3.png" alt="Linear-probe ROC-AUC before and after SFT"/>
  <figcaption><strong>Figure 4.</strong> Linear-probe ROC-AUC before and after SFT for benign signals, risk signals, and attribute groups.</figcaption>
</figure>
<h4>SFT makes benign signals more accessible</h4>
<p>Figure 4 shows that linear-probe ROC-AUC improves more for benign signals (0.583 -&gt; 0.642) than for risk signals (0.702 -&gt; 0.729). Before SFT, benign signals are close to chance (0.5) in the base model&#x27;s representations, so the model carries little information about what a legitimate business looks like. After SFT, eight of the nine benign signals become easier to recover, including a consistent business description (0.614 -&gt; 0.707) and an established processing history (0.644 -&gt; 0.712).</p>
<p>These results suggest that SFT helps the model recognize when suspicious-looking activity makes sense for a legitimate business. A model that better recognizes legitimate businesses is less likely to flag them, which aligns with the higher precision at the same recall in Figure 3 and reduces false positives.</p>
<h3>SFT improves risk assessments across tasks</h3>
<p>Across multiple risk assessment tasks at Stripe, supervised fine-tuning increased precision by more than 21% at the same recall and consistently outperformed XGBoost. These results make SFT a strong foundation for production risk systems that require both precise risk evaluations and the flexibility to use rich, heterogeneous evidence.</p>]]></content:encoded></item><item><title><![CDATA[Creating a whitelabeled connected account dashboard with embedded components]]></title><description><![CDATA[This post shows users how to build a whitelabeled dashboard for connected accounts with just a few lines of code by using Connect embedded components.]]></description><link>https://stripe.dev/blog/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components</link><guid isPermaLink="true">https://stripe.dev/blog/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components</guid><dc:creator><![CDATA[Jorge Aguirre Gonzalez]]></dc:creator><pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image5.png"/><link rel="preload" as="image" href="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image2.png"/><link rel="preload" as="image" href="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image1.png"/><link rel="preload" as="image" href="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image9.png"/><link rel="preload" as="image" href="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image7.png"/><link rel="preload" as="image" href="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image3.png"/><link rel="preload" as="image" href="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image4.png"/><link rel="preload" as="image" href="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image6.png"/><link rel="preload" as="image" href="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image8.png"/><p>In a <a href="https://stripe.dev/blog/connect-embedded-components-streamline-onboarding">past blog post</a>, we explored how to move from hosted onboarding onto a whitelabeled embedded onboarding flow by using the <a href="https://docs.stripe.com/connect/supported-embedded-components/account-onboarding">account onboarding embedded component</a>. In this blog post, we’ll go over how to build a whitelabeled dashboard for connected accounts with just a few lines of code by using Connect embedded components.</p>
<p>In this example, we’ll be building out the connected account dashboard for FurEver, a pet grooming SaaS platform.</p>
<h2>Rendering payments, allowing refunds and dispute management</h2>
<p>To achieve this in the FurEver dashboard, you could build these workflows directly with Stripe’s APIs, but doing so requires substantially more than listing <a href="https://docs.stripe.com/api/payment_intents/list">PaymentIntents</a>. You would need to implement pagination, payment-method and status presentation, permissions, payment details, refunds, disputes, capture flows, and ongoing support for new states and capabilities. The <a href="https://docs.stripe.com/connect/supported-embedded-components/payments">Payments embedded component</a> provides these workflows as a Stripe-maintained UI that you can place directly inside your dashboard.</p>
<h3>One-time setup</h3>
<p>First, we need to make sure we’re initializing embedded components correctly for the connected account. We do this by exposing an endpoint on the FurEver dashboard server-side code that creates an account session for the current account. FurEver is a NextJS application that uses App Router, so we add a new <code>/api/create_account_session</code> POST endpoint:</p>
<pre><code class="language-javascript">export async function POST() {
  const session = await getServerSession(authOptions); // Get FurEver session state
  const stripeAccountId = session.user.stripeAccount.id;
  const accountSession = await stripe.accountSessions.create({
    account: stripeAccountId,
    components: {
      payments: {
        enabled: true,
      },
      // Other components go here...
    },
  });

  return new Response(JSON.stringify(accountSession), {
    status: 200,
    headers: {&#x27;Content-Type&#x27;: &#x27;application/json&#x27;},
  });
}
</code></pre>
<p>We&#x27;ll use this API to initialize embedded components on the frontend. I’ll need to add a dependency to the <code>@stripe/connect-js</code> and <code>@stripe/react-connect-js</code> packages.</p>
<pre><code class="language-javascript">yarn add @stripe/connect-js @stripe/react-connect-js
</code></pre>
<p>Now that we have the packages, I’m going to edit the top-level wrapper of my frontend to include the initialization context for embedded components. This makes it so that I can render components in any part of the React tree below this wrapper.</p>
<pre><code class="language-javascript">import {loadConnectAndInitialize} from &#x27;@stripe/connect-js&#x27;;
import {
  ConnectComponentsProvider,
} from &#x27;@stripe/react-connect-js&#x27;;

export default function AppWrapper({children}) {
  const [stripeConnectInstance] = React.useState(() =&gt; {
    const fetchClientSecret = async () =&gt; {
      const response = await fetch(&#x27;/api/create_account_session&#x27;, {
        method: &#x27;POST&#x27;,
      });
      if (!response.ok) {
        throw new Error(&#x27;Failed to fetch account session&#x27;);
      } else {
        const {client_secret} = await response.json();
        return client_secret;
      }
    };

    return loadConnectAndInitialize({
      publishableKey:
        &#x27;pk_test_123&#x27;, // Your publishable key goes here!
      fetchClientSecret: fetchClientSecret,
    });
  });

  return (
    &lt;ConnectComponentsProvider connectInstance={stripeConnectInstance}&gt;
      {children}
    &lt;/ConnectComponentsProvider&gt;
  );
}
</code></pre>
<p>Next, we can render the ConnectPayments embedded component in the payments page:</p>
<pre><code class="language-javascript">import {
  ConnectPayments,
} from &#x27;@stripe/react-connect-js&#x27;;

export const PaymentsPage = () =&gt; {
  return (
    &lt;div&gt;
       &lt;div&gt;...Header...&lt;/div&gt;
       &lt;div&gt;
          &lt;div&gt;Recent payments&lt;/div&gt;
          &lt;ConnectPayments /&gt;
       &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><img src="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image5.png" alt="The ConnectPayments embedded component in the payments page"/></p>
<p>The payments component is now rendering under the “Recent payments” label. Connected accounts can now refund payments, respond to disputes, and capture payments.</p>
<h3>Rendering balance and payouts</h3>
<p>Let’s give connected accounts an overview of their balance and payouts by using the payouts component. On the server, we should enable the payouts component:</p>
<pre><code class="language-javascript">const accountSession = await stripe.accountSessions.create({
  account: stripeAccountId,
  components: {
    payments: {
      enabled: true,
    },
    payouts: {
      enabled: true,
    },
    // Other components go here...
  },
});

// PayoutsPage.tsx
import {
  ConnectPayouts,
} from &#x27;@stripe/react-connect-js&#x27;;

export const PayoutsPage = () =&gt; {
  return (
    &lt;div&gt;
       &lt;div&gt;...Header...&lt;/div&gt;
       &lt;div&gt;
          &lt;div&gt;Payouts&lt;/div&gt;
          &lt;ConnectPayouts /&gt;
       &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><img src="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image2.png" alt="ConnectPayments embedded component in the payments page"/></p>
<p>Connected accounts can now view balance, payouts, change payout schedule, and change their bank destination.</p>
<h3>Keeping connected accounts enabled for payments</h3>
<p>Connected accounts may become unable to process payments or obtain payouts for a wide variety of reasons - exceeding processing thresholds, balance overdrafts, or changing regulations, which require additional information from accounts to process payments. To keep accounts enabled, we can automate the information collection flows by enabling the <code>notification_banner</code> component, and render it in our app header.</p>
<p>The <code>notification_banner</code> is conditionally rendered - meaning it will only render when the account is disabled and needs additional information.</p>
<pre><code class="language-javascript">const accountSession = await stripe.accountSessions.create({
  account: stripeAccountId,
  components: {
    payments: {
      enabled: true,
    },
    payouts: {
      enabled: true,
    },
    notification_banner: {
      enabled: true,
    },
    // Other components go here...
  },
});

// AppHeader.tsx
import {
  ConnectNotificationBanner,
} from &#x27;@stripe/react-connect-js&#x27;;

export const AppHeader = ({ children } ) =&gt; {
  return (
    &lt;div&gt;
       &lt;ConnectNotificationBanner /&gt;
       &lt;&gt;{children}&lt;/&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><img src="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image1.png" alt="The notification_banner component"/></p>
<p>The notification banner (shown within the outline for clarity) now renders when a requirement is present on the connected account, to keep payments and payouts enabled.</p>
<h3>Mapping platform user roles to account session features</h3>
<p>So far we’ve used components configured with default <a href="https://docs.stripe.com/api/account_sessions/create">account session</a> features. In the case of FurEver, we have admin users and regular users, and we want to toggle features depending on the user role. For example, only admin users should be able to perform refunds on the payments component:</p>
<pre><code class="language-javascript">export async function POST() {
  const session = await getServerSession(authOptions); // Get FurEver session state
  const isAdmin = session.user.isAdmin;
  const stripeAccountId = session.user.stripeAccount.id;
  const accountSession = await stripe.accountSessions.create({
    account: stripeAccountId,
    components: {
      payments: {
        enabled: true,
        features: {
          refund_management: isAdmin,
          dispute_management: isAdmin,
        },
      },
      payouts: {
        enabled: true,
        features: {
          edit_payout_schedule: isAdmin,
        },
      },
      notification_banner: {
        enabled: true,
      },
      // Other components go here...
    },
  });

  return new Response(JSON.stringify(accountSession), {
    status: 200,
    headers: {&#x27;Content-Type&#x27;: &#x27;application/json&#x27;},
  });
}
</code></pre>
<p>The account session is now configured correctly to respect admin user roles, and components automatically adjust their behavior depending on this configuration.</p>
<p><img src="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image9.png" alt="The refund flow is only enabled for FurEver admins"/>
<img src="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image7.png" alt="The refund flow is only enabled for FurEver admins"/></p>
<p>The refund flow is only enabled for FurEver admins.</p>
<h3>Matching the look and feel of the platform site</h3>
<p>By using the <a href="https://docs.stripe.com/connect/customize-connect-embedded-components">appearance API</a>, we can get the components to look exactly like the rest of the UI. We recommend using the “upload” functionality of our <a href="https://docs.stripe.com/connect/customize-connect-embedded-components">theming doc site</a> which can take an image of your site, and output theming values you can fine tune.</p>
<p><img src="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image3.png" alt="Using the appearance API"/></p>
<p>FurEver uses the following theme, and toggles it depending on whether the app is in dark or light mode - it also passes in the current locale, to ensure this matches the rest of the dashboard.</p>
<pre><code class="language-javascript">export const defaultPrimaryColor = &#x27;#27AE60&#x27;;

export const LightTheme = {
  fontFamily: &#x27;Sohne, inherit&#x27;,
  colorPrimary: defaultPrimaryColor,
  colorBackground: &#x27;#ffffff&#x27;,
  colorBorder: &#x27;#D8DEE4&#x27;,
  ...
  // See full light theme [here](https://github.com/stripe/stripe-connect-furever-demo/blob/6c99740f14bf9d76d127aa0495cf38b9ab72ff3b/app/contexts/themes/ThemeConstants.ts#L3)
};

export const DarkTheme = {
  fontFamily: &#x27;Sohne, inherit&#x27;,
  colorPrimary: defaultPrimaryColor,
  colorBackground: &#x27;#1e222a&#x27;,
  ...
  // See full dark theme [here](https://github.com/stripe/stripe-connect-furever-demo/blob/6c99740f14bf9d76d127aa0495cf38b9ab72ff3b/app/contexts/themes/ThemeConstants.ts#L23)
};
...

const appearanceVariables = theme === &#x27;dark&#x27; ? DarkTheme : LightTheme; // Toggle depending on current FurEver theme
const instance = loadConnectAndInitialize({
  ...
  appearance: {
    overlays: &#x27;dialog&#x27;,
    variables: appearanceVariables || LightTheme,
  },
  locale,
  ...
});
</code></pre>
<h3>Conclusion</h3>
<p>By using embedded components and writing only a few lines of code, we’ve built a full payments dashboard for FurEver connected accounts that:</p>
<ol>
<li>Provides connected accounts with full functionality for payments, payouts and keeping accounts enabled via notification banner</li>
<li>Snaps to FurEver user role permissions by using account session features</li>
<li>Matches the FurEver look and feel by using appearance and locale</li>
<li>Updates automatically, so FurEver won’t miss any new payment methods or compliance updates</li>
</ol>
<p><img src="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image4.png" alt="Payments dashboard for FurEver connected accounts"/>
<img src="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image6.png" alt="Payments dashboard for FurEver connected accounts"/>
<img src="https://stripe.dev/images/creating-a-whitelabeled-connected-account-dashboard-with-embedded-components/image8.png" alt="Payments dashboard for FurEver connected accounts"/></p>
<p>You can try this demo live at <a href="https://furever.dev/">https://furever.dev</a>. This sample is open source: <a href="https://github.com/stripe/stripe-connect-furever-demo">https://github.com/stripe/stripe-connect-furever-demo</a>.</p>
<p>In this blog post we’ve used only three components. Stripe offers a growing catalog of embedded components, including components for areas such as Instant payouts, Capital, Tax, and Issuing. To see the full component list and read more about how to integrate with them please visit our <a href="https://docs.stripe.com/connect/supported-embedded-components">supported components page</a>.</p>]]></content:encoded></item><item><title><![CDATA[Stripe’s Payment Method Factory: Orchestrating agents for repeated, custom integrations]]></title><description><![CDATA[Stripe built an agentic payment method factory from over 100 reusable prompts, hardened by agents observing implementation runs, and kept them current by feeding back each run's learnings.]]></description><link>https://stripe.dev/blog/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations</link><guid isPermaLink="true">https://stripe.dev/blog/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations</guid><dc:creator><![CDATA[David Dunne]]></dc:creator><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image4.png"/><link rel="preload" as="image" href="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image6.png"/><link rel="preload" as="image" href="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image2.png"/><link rel="preload" as="image" href="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image1.png"/><link rel="preload" as="image" href="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image5.png"/><p>The first time you build something new, you start from scratch. The second time, you spot the patterns and build a platform. But to truly scale, you build a factory.</p>
<p>We built our payment method factory from over 100 reusable prompts, hardened by agents observing implementation runs, and kept them current by feeding back each run&#x27;s learnings.</p>
<p>Stepping back, Stripe offers over 125 payment methods across the globe, ranging from credit cards, to PayPal wallets, to Buy Now, Pay Later services like Klarna. At a high level, each payment method does the same thing:</p>
<ul>
<li>Makes a payment</li>
<li>Issues a refund</li>
<li>Handles a dispute</li>
</ul>
<p>Under the hood, they have different behaviors and different API shapes. Despite these differences, the process of building each integration follows a familiar pattern. Yet with little reusable tooling, teams were starting from scratch each time, meaning it could take six months to build even a basic integration.</p>
<p>In 2025, this led us to ask two important questions.</p>
<ul>
<li>Could we prompt an LLM to build an entire payment method integration?</li>
<li>And, more importantly, could we reuse those prompts for every future integration?</li>
</ul>
<p>The factory we built has so far created three new payment method integrations and migrated ten existing integrations to our modern stack, demonstrating its effectiveness for legacy migrations, too. The payment method factory reduced integration timelines from as long as six months to two-to-six weeks.</p>
<p>Along the way, we learned how valuable it was to harden our prompts to make them reusable, keep them up to date as the codebase evolved, orchestrate the agents in a human-guided workflow, and get agents testing their own changes.</p>
<h2>Building the foundation: Hardened reusable ‘one-shot’ prompts</h2>
<p>An agent skill and a simple prompt are often enough to complete part of an integration. If the agent needs steering, we can refine the original prompt until it completes the task without our help. Saving these prompts for the next integration makes sense, but how do we know whether they’re actually good prompts, or whether the agent just muddled its way through despite them? Even in a successful ‘one-shot’ run, we can still see the agent perform unnecessary investigations or repeatedly attempt to fix failing tests. We hypothesized that evaluating and refining these prompts could get us the same results faster, at a lower cost, and with more confidence in the output.</p>
<p>To see how much difference this approach could make, we did a bake-off, two engineers completed the same integration task: add payment method support for a frontend-facing API. One engineer used vanilla Claude Code, while the other used Claude Code with a reusable set of prompts built from our prior experience. The difference was substantial: the vanilla setup required roughly 20 days of engineering effort, compared to 4 days when using the reusable prompts.</p>
<h3>Writing a good one-shot prompt</h3>
<p>We began with over 125 past implementations of payment methods as the base of our prompt. We gave the agent these examples and a template for structuring the prompt, then refined the output by hand. We designed the resulting template to be payment-method agnostic so it could be reused across other payment methods. Here’s an example:</p>
<p><img src="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image4.png" alt=""/></p>
<p>Excerpt from a prompt for handling payment confirmations</p>
<p>This gave us a strong starting point for evaluating and hardening the prompt. The process starts with an implementation agent that’s following the prompt to build a payment method integration. A second agent, acting as an observer, runs in parallel to the implementation agent. The observer knows what the agent is trying to implement and what the end state should look like. It reads the implementation agent’s transcript, and notes down which steps took extra time to investigate, where the agent got lost, and what the agent had to re-learn. It checks if any of our internal tooling failed during the process, like a command timing out. If the first pass generated a red CI, it looks at the failures. The engineer takes the observer agent’s report and refines the prompt further.</p>
<p><img src="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image6.png" alt=""/></p>
<p>The first step when starting on a new payment method integration is the scaffolding, which  could take an engineer up to a week to finish. It involves adding an ‘adapter’ so that other Stripe products can interact with the payment method, adding feature gates to restrict access, wiring it in our API specs and SDKs without exposing it, and setting the partner details. This change alone is more than 5000 lines long, touches over 100 files, and is produced in part by 15 commands that generate code and have to run in a set order. When something broke, an engineer had to work out what each command does and where the payment method still needed to be registered. With a prompt that explains what each piece does and where to look if something breaks, we brought this step down to a day of implementation time.</p>
<h3>Continuous knowledge loop: Keeping prompts up to date</h3>
<p>The issue with reusable prompts, unlike creating a new one each time, is that we have to find a way to effectively maintain them. We now have over 100 saved prompts. If the way we provision databases changes, or an investigation pointer isn’t valid, we need to update any affected prompts.</p>
<p>To take on this maintenance overhead, we developed a system that continuously improves the prompts. While the payment method factory is working on an integration, it collects some artifacts:</p>
<ul>
<li>Learning logs from the individual implementation agents about anything they had to learn about our systems that wasn’t covered by the initial prompt.</li>
<li>Decision logs for all the decisions they had to make unattended to give the reviewers more context on why the agent went down a certain path.</li>
<li>Full transcripts of the entire session.</li>
<li>Engineer’s review comments on the created pull request.</li>
</ul>
<p>When the integration is complete, an agent takes these artifacts and suggests potential improvements to the prompts. Some are straightforward enough and can be implemented with the click of a single button using <a href="https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents">minions</a>, Stripe’s one-shot coding agents, while others might require further work before we apply them.</p>
<p><img src="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image2.png" alt=""/></p>
<h2>Building the factory: Orchestrating our collection of prompts</h2>
<p>We now have a collection of prompts that covers every step of building a new payment method integration. Stripe uses a monorepo, so theoretically, we could combine all the prompts into one master prompt and be done in one pull request. In practice, the current limits of an agent’s context window make this impossible and the resulting PR would be too large to review and prone to merge conflicts.</p>
<p>We kept the development lifecycle we already know works: start an agentic session, run a prompt, review the result, and open a PR. The difference is that an engineer no longer has to drive every step themselves. An orchestrator can delegate each step to an isolated implementation agent.</p>
<p>There are two phases to our orchestration. The first is creating a plan, a sequence of discrete steps, each linked to one of the prompts we developed earlier. The goal is for each step to map to one pull request. As we construct the plan, the agent interviews the engineer for context on the payment method being built, asking about requirements like what currencies are supported in which regions. This context is exported to a shared knowledge base that implementation agents can later reference.</p>
<p><img src="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image1.png" alt=""/></p>
<p>The second phase executes the plan, with implementation (sub)agents handling each step. To coordinate their work, we needed an orchestrator that could assign each task to the right implementation agent and track its progress.</p>
<p>Our first approach used a high-level orchestrator agent for this role. It worked, but it was expensive, slow, and sometimes unreliable. In some cases, the orchestrator agent tried to implement the step itself rather than delegating to an implementation agent, which ultimately ate up its context.</p>
<p>We learned that although an LLM can orchestrate a workflow, predictable coordination is better implemented in code.</p>
<p><img src="https://stripe.dev/images/stripes-payment-method-factory-orchestrating-agents-for-repeated-custom-integrations/image5.png" alt=""/></p>
<h2>Building confidence: Letting agents test their own work</h2>
<p>As code generation becomes faster, code reviews started becoming a bottleneck. We help reviewers by keeping pull requests small and providing all relevant context, but ultimately, we found that giving agents the tools to test their integrations autonomously gives the reviewer the most confidence.</p>
<p>Let’s take the service that talks with the payment method provider as an example. If a provider has a sandbox environment, the factory creates test payments and runs different end-to-end scenarios against it. The factory checks requests, responses, internal state changes, and emitted events against our expectations. It then snapshots the provider’s API responses and creates immutable contracts that are used throughout our stack, rather than manually creating hand-written mocks. All of this is then presented to the engineer and is used in our system to emulate recorded payment method behavior.</p>
<p>We still have room to improve here. Agents should have the tools to interact with every part of our systems and we’d like to automate remaining manually tested steps, like UI testing.</p>
<h2>Beyond payment methods</h2>
<p>Payment methods are not the only repeated, customizable integration we have. Teams at Stripe were inspired by the payment method factory to build their own versions. For example, our tax team uses one to build connectors that bring Stripe Tax to businesses selling on other platforms, and our verification team built one for the providers we use to check business details during onboarding.</p>
<p>If making other engineers more productive interests you, <a href="https://grnh.se/b5wzjpge1us">we’re hiring</a>.</p>]]></content:encoded></item><item><title><![CDATA[How Stripe is designing Checkout for AI agents]]></title><description><![CDATA[How Stripe used WebMCP to accelerate agent purchases from the browser.]]></description><link>https://stripe.dev/blog/how-stripe-is-designing-checkout-for-ai-agents</link><guid isPermaLink="true">https://stripe.dev/blog/how-stripe-is-designing-checkout-for-ai-agents</guid><dc:creator><![CDATA[Cara Mecozzi]]></dc:creator><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image2.png"/><link rel="preload" as="image" href="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image3.gif"/><link rel="preload" as="image" href="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image4.png"/><link rel="preload" as="image" href="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image1.png"/><link rel="preload" as="image" href="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image5.png"/><link rel="preload" as="image" href="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image6.png"/><p>In order to help Stripe sellers benefit from the rapid growth of AI agents, we enabled WebMCP on all Stripe Checkout UIs resulting in 42% fewer tokens consumed and 38% fewer tool calls invoked against baseline measurements.</p>
<p>While many businesses (Stripe included!) have built MCP servers as a programmatic interface for agents, browser use remains a predominant path for agents to interface with existing services. Websites, traditionally, have been optimized for human usage. To complete a checkout, an agent must interpret both the visual interface and the Document Object Model (DOM), infer both the information presented and which actions are available, and then plan and execute those actions. Even with ongoing model improvement, navigating a webpage is still slow, indirect, and token-intensive.</p>
<p>Just as we optimize human buying experiences to minimize latency and clicks, we need to optimize agent buying experiences to minimize token usage and tool calls. These are the primary measures of agent efficiency: tokens indicate cost, while tool calls indicate effort (and a high number of tool calls can also signal confusion!).</p>
<p>We believed WebMCP, an emerging standard gaining traction, was a strong candidate to improve the agent checkout experience.</p>
<p>This problem is no longer hypothetical. Agents like <a href="https://stripe.com/newsroom/news/stripe-helps-meta-muse-shop-with-link">Muse</a>, Instinct, and Grokbot, are launching and becoming popular – typically equipped with a virtual machine and browser –  and we are seeing and anticipating growth in agentic volume processed on Stripe:</p>
<p><img src="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image2.png" alt=""/></p>
<p>We decided to incorporate agent-friendly WebMCP across Stripe Checkout UIs, which powers over 7.8M businesses processing around 0.45% of global GDP.  We aimed to give millions of merchants access to future agentic shoppers without them needing to change their integration while providing a fast and reliable consumer experience.</p>
<p>As a baseline without WebMCP enabled, we measured an average of 1.8M tokens consumed, 38.83 tool invocations, and &gt;2.5 minutes duration for these agents to complete a transaction with Stripe Checkout. Even in chat-based applications where there is some expectation of latency, this is all simply too high.</p>
<p>This blog post focuses on how we implemented WebMCP to reduce token consumption and tool invocations.  We struck a balance between building a specialized interface that improved agent performance, yet keeping it aligned to the human interface for better maintainability.</p>
<h2>What is WebMCP?</h2>
<p><a href="https://developer.chrome.com/docs/ai/webmcp">WebMCP</a> is a browser technology that lets websites “expose” MCP tools to agents from the client side. Instead of inferring how to complete a task and navigating the DOM, an agent can execute tools, such as get_order_summary or fill_payment_form. The agent doesn’t necessarily have to look at any of the HTML on the page.</p>
<p><img src="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image3.gif" alt=""/></p>
<p>Above is a Stripe Checkout page.  We can see that invocations of the available tools result in changes to the UI – select_payment_method dynamically updates the UI and fill_payment_form put in credentials like the card number – we theorized this is a great improvement over ingesting all of the HTML into the LLM context and ascertaining element ids or classes to infer where buttons were (or hardcoding flows that could be brittle).</p>
<h2>Building for WebMCP</h2>
<p>With a baseline measurement of ~39 tool calls to reach payment, each consuming tokens and time, we focused on tool design that would substantially reduce that number. And for our initial build, we preferred not to “fork” the business logic behind the page depending on whether the user was a human or an agent.</p>
<p>A catalog of tools can eliminate some of the ambiguity that comes with visual browser automation, but with a large catalog, the agent must still decide which actions are valid. An agent presented with too many tools, irrelevant parameters, or invalid actions can still struggle to complete the task.</p>
<p>Consider a checkout that supports multiple payment methods, dynamically rendered address fields, and a payment button that is only activated after the required information has been provided. If an agent receives every possible action at once, its context includes tools and parameters that may be irrelevant or impossible to use.</p>
<p>Instructions in the tool descriptions can help, but they still leave the agent unnecessarily responsible for reasoning about constraints the application already understands. You can think of it like pruning the branches of a tree to  progressively guide the agent towards the target outcome.</p>
<h3>Reveal tools as they become actionable</h3>
<p>We designed Stripe’s WebMCP implementation around progressive tool disclosure, meaning that the page only exposes only the tools and parameters that are relevant in its current state. This makes agent interactions more reliable while keeping human and agent experiences aligned. Concretely, we want to minimize cases where available tools can result in sequenced invocations that result in errors.</p>
<p>As an example, we do not reveal the submit_payment tool until all the fields have been filled. A simplified checkout might progress like this:</p>
<ol>
<li>The agent inspects the checkout session and available payment methods.</li>
<li>After selecting a payment method, the page exposes a form filling tool whose schema reflects the visible fields.</li>
<li>The agent provides the required information using that tool.</li>
<li>Once the form is complete and valid, the page exposes submit_payment.</li>
</ol>
<p>If the checkout state changes, the available tools change with it. For example, if the agent changes payment method type to a non-card method, we’ll remove the number from the parameter set of fill_payment_form.</p>
<p>Rather than mapping every possible action upfront, this approach reduces tool calls, improves reliability, and increases successful payment outcomes.</p>
<p>Additionally, we relied on many of the existing accessibility patterns and optimizations in our frontend code to keep a shared infrastructure rather than creating a second checkout implementation exclusively for agentsWebMCP tools delegate to the same foundations used by human-facing checkout interfaces. As we continue to optimize Stripe Checkout for humans, agents will reap all the benefits. And, now that we’re closer to human and agent parity, we’re interested in how agent-first improvements may reflect in the human experience.</p>
<h3>Avoid forking our business logic</h3>
<p>We used two WebMCP API types to connect tools to the existing interface: imperative and declarative tool implementations. Our existing UI surfaces have both state management and forms designed to handle the end-to-end payment flow. We did not want to introduce an “agent-only” bifurcation that could result in drift or add toil for engineers.</p>
<p>With the Imperative API, we introduced tools like get_order_summary that derive their responses from existing state and select_payment_method for explicit actions like changing payment method that initiate existing state transitions.</p>
<p>For form filling, we used the Declarative API to create tools that derive from rendered HTML. The browser can infer an input schema from the controls that are currently visible rather than requiring us to maintain a second, manually duplicated form schema. This means that if a developer adds a new form field, it will bubble up to the new tools by default.</p>
<p>Agents using browser automation tools like Browserbase can then invoke these tools directly to get checkout information and complete payments.</p>
<h2>Evaluating performance</h2>
<p>To assess whether WebMCP made agents attempting to checkout more efficient without sacrificing reliability, we measured checkout completion, execution time, tool calls, and token consumption.</p>
<p>We ran 60 tests across six models on a fictional outdoor retailer website using the Stripe Checkout full page UI. 50% of the tests used WebMCP and 50% used a baseline browser interaction. Each test required the agent to complete the same end-to-end task to navigate to the website, add a product to the cart, proceed to checkout and complete payment.</p>
<p>In all of the tests, agents completed the checkout successfully. But when we compared the agents that used WebMCP, we found that agents performed significantly better across three categories.</p>
<p><img src="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image4.png" alt=""/></p>
<p>Agents using WebMCP consumed 42% fewer tokens, completed checkout 39% faster, and made 38% fewer tool calls per run.</p>
<p><img src="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image1.png" alt=""/></p>
<p>We measured total tokens (input + output + cache) consumed by the agent to complete the task. Token usage contributes directly to the cost of an agentic transaction, so reducing tokens can make these transactions less expensive for agents to support at scale.</p>
<p><img src="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image5.png" alt=""/></p>
<p>We also counted the number of actions (clicks, form fills, tool invocations) an agent takes to complete checkout. Fewer tool calls mean the agent took a more direct path through checkout, with less exploratory form interaction.</p>
<p><img src="https://stripe.dev/images/how-stripe-is-designing-checkout-for-ai-agents/image6.png" alt=""/></p>
<p>And finally, we measured wall-clock time from task start to a completed checkout. This looked at the time it took for the agent to navigate to the URL, select a product, complete the checkout form and submit the payment. The reduction in execution time lowered the time a customer spent waiting for an agent to complete a purchase by 60 seconds.</p>
<h2>Up next</h2>
<p>WebMCP is one step in an evolving standard of browser-agent behavior, where interfaces can serve people and agents without requiring separate experiences for each. As agents become an even more popular way people interact with the web, engineers will need to decide how to communicate desired actions to both human and machine users.</p>
<p>As WebMCP and browser agents evolve, we’ll continue improving Checkout for agent-driven purchases to help businesses serve customers however they choose to buy, whether they interact with Checkout directly or ask an agent to act on their behalf.</p>
<p>Ready to build for agent driven purchases? <a href="https://docs.stripe.com/agentic-commerce/for-agents/webmcp">Get started with Stripe Checkout and WebMCP</a>.</p>
<p>We’re still early in understanding what becomes possible when it comes to building for AI agents. There’s a lot left to build at Stripe, and the work spans infrastructure, developer tools, product systems, and AI all at once. If that’s the kind of problem you want to work on, <a href="https://grnh.se/b5wzjpge1us">we’re hiring</a>.</p>]]></content:encoded></item><item><title><![CDATA[Building custom financial reconciliation workflows with Stripe]]></title><description><![CDATA[Stripe’s next generation of reporting APIs are focused on enterprise scalability, and this post shows you how to use them in your financial reporting infrastructure.]]></description><link>https://stripe.dev/blog/building-custom-financial-reconciliation-workflows-with-stripe</link><guid isPermaLink="true">https://stripe.dev/blog/building-custom-financial-reconciliation-workflows-with-stripe</guid><dc:creator><![CDATA[Maia Robinson]]></dc:creator><pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Many traditional enterprises still rely on SSH File Transfer Protocol (<a href="https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol">SFTP</a>) for core financial reconciliation activities that are critical for finance teams. While SFTP has remained a common standard, it falls short in the age of event-driven architecture, hyper-customization, and continuous iteration. Stripe’s next generation of reporting APIs are focused on enterprise scalability, and this post shows you how to use them in your financial reporting infrastructure.</p>
<h2>Why SFTP falls short</h2>
<p>It is true that SFTP remains common in enterprise environments. However, we consistently hear that SFTP proves to be fragile, hard to scale, and lacks the level of self-service customization that complex, dynamic businesses need.</p>
<ol>
<li>Polling dependency and data delays: Statically scheduled batch jobs can leave data behind operational reality. SFTP reporting pipelines are often coupled with cron jobs and polling processes to detect new files, which is inefficient when Stripe offers real-time payout and report run completion events.</li>
<li>Customization limitations: Oftentimes, legacy processors have SFTP reports with a fixed set of available columns and set delivery schedules. With Stripe’s API-based custom reports, when your finance team makes a request for a new report field or cadence, there’s no need to go back and forth with Stripe, you can just implement it.</li>
<li>Security overhead: Reporting APIs allow you to use the same API key-based permissioning and audit trail as the rest of your services, not a separate SFTP credential/IP-allowlist to manage.</li>
</ol>
<h2>Stripe’s reporting philosophy</h2>
<p>Stripe’s reporting APIs, including the <a href="https://docs.stripe.com/reports/v2-api">Reports API v2</a> and <a href="https://docs.stripe.com/reports/query-runs">Query Run API</a>, are focused on delivering a developer-first approach to financial reconciliation.</p>
<ol>
<li>Data platform consistency: Businesses often have completely separate datasets and schemas being used for financial reconciliation activities versus BI analytics. By offering a consistent schema across data products like Sigma and Data Pipeline, which also closely align with the API conventions, Stripe provides a more consistent experience across your business’ use cases.</li>
<li>Self-service: Developer agency is a non-negotiable for Stripe products, and reporting is no different. Whether you need to add custom metadata as a report column, join multiple resource tables, add a new derived field, or update the report run schedule, you can make changes autonomously.</li>
</ol>
<h2>Building financial reconciliation reports</h2>
<p>Let’s take a fictional business, Galtee, an outdoor equipment retailer. Galtee has a direct-to-consumer business line as well as a business-to-business line that sells products to campgrounds and adventure tourism companies. The Galtee finance team has shared financial reconciliation reporting requirements that Galtee’s engineering team is working on fulfilling using <a href="https://docs.stripe.com/data/sigma">Sigma</a>. The <a href="https://docs.stripe.com/reports/query-runs">Query Run API</a> allows Galtee to programmatically execute SQL queries as part of a reporting workflow, making it possible to create fully custom reports that don’t need a post-extraction transformation layer.</p>
<h3>Multi-account payout reconciliation</h3>
<p>Galtee has created two different Stripe accounts, representing the D2C and B2B business lines, that are grouped under an <a href="https://docs.stripe.com/get-started/account/orgs">Organization</a>. Galtee’s finance team needs a report that includes transaction information for all funds paid out to the Galtee bank accounts for a given day.</p>
<p>The <code>balance_transactions</code> Sigma table is the main table for financial reconciliation, because it includes all transactions that contributed to the Stripe account balance. It can be joined with the transfers table to list all transactions that are part of a bank payout on a given date:</p>
<pre><code class="language-sql">SELECT
  date(transfers.date) as payout_date,
  transfers.id as payout_id,
  balance_transactions.id AS balance_transaction_id,
  balance_transactions.source_id,
  balance_transactions.type,
  balance_transactions.net,
  balance_transactions.currency,
  balance_transactions.amount
FROM transfers
JOIN balance_transactions
  ON balance_transactions.automatic_transfer_id = transfers.id
WHERE
  date(transfers.date) = date({{PAYOUT_REPORT_DATE}})
</code></pre>
<p>Then, to run this query across both of Galtee’s Stripe accounts, Galtee can set the API key to be the Organization secret key:</p>
<pre><code class="language-bash">curl -X POST https://api.stripe.com/v2/data/reporting/query_runs \
  -H &quot;Authorization: Bearer {{ORGANIZATION_SECRET_KEY}}&quot; \
  -H &quot;Stripe-Version: 2026-07-29.preview&quot; \
  --json &#x27;{
    &quot;sql&quot;: &quot;{{QUERY_STRING}}&quot;
  }&#x27;
</code></pre>
<p>Once the query completes, Galtee will receive a <a href="https://docs.stripe.com/api/v2/data/reporting/query-runs/event-types?api-version=2026-07-29.preview#v2_query_runs_event_types-v2.data.reporting.query_run.succeeded">v2.data.reporting.query_run.succeeded</a> webhook event that can trigger automated report retrieval.</p>
<p>This returns a clean table listing all balance-affecting activity, including payments, refunds, and fees included in a given payout. The sum of the “net” column amounts, minus the successful payout rows, should equal the total cash paid out to Galtee’s bank accounts that day.</p>
<h3>Local presentment and FX in payout reconciliation</h3>
<p>Now, let’s say Galtee has started selling cross-border to European buyers and is manually setting prices in EUR. Galtee’s finance team now needs the payout reconciliation report updated to include the presentment amounts and the exchange rate used for the currency conversion of each transaction. This can be easily incorporated by adding the <code>exchange_rate</code> field from the <code>balance_transactions</code> table and adding the presentment data by joining with the charges table:</p>
<pre><code class="language-sql">SELECT
  date(transfers.date) as payout_date,
  transfers.id as payout_id,
  charges.id AS charge_id,
  charges.created AS charge_created_at,
  balance_transactions.id AS balance_transaction_id,
  balance_transactions.source_id,
  balance_transactions.type,
  balance_transactions.net,
  balance_transactions.currency AS settlement_currency,
  balance_transactions.amount AS settlement_amount,
  charges.currency AS presentment_currency,
  charges.amount AS presentment_amount,
  balance_transactions.exchange_rate
FROM transfers
JOIN balance_transactions
  ON balance_transactions.automatic_transfer_id = transfers.id
LEFT JOIN charges
  ON charges.balance_transaction_id = balance_transactions.id
WHERE
  date(transfers.date) = date({{PAYOUT_REPORT_DATE}})
</code></pre>
<p>Now the reconciliation report supports local currency presentment scenarios and Galtee’s engineering team can immediately deploy this change by updating their <a href="https://docs.stripe.com/reports/query-runs">Query Run API</a> sql parameter.</p>
<h3>Itemized IC+ transaction fees in payout reconciliation</h3>
<p>Next, Galtee’s finance team wants to know the total IC+ fees associated with each card charge on the payout report. The original query can be modified to also join with the <code>icplus_fees</code> table and sum all of the IC+ fee rows for each charge:</p>
<pre><code class="language-sql">SELECT
  date(transfers.date) as payout_date,
  transfers.id as payout_id,
  balance_transactions.id AS balance_transaction_id,
  balance_transactions.source_id,
  balance_transactions.type,
  balance_transactions.net,
  balance_transactions.currency AS settlement_currency,
  balance_transactions.amount AS settlement_amount,
  charges.currency AS presentment_currency,
  charges.amount AS presentment_amount,
  balance_transactions.exchange_rate,
  CASE
    WHEN balance_transactions.type = &#x27;charge&#x27; THEN (
      SELECT SUM(icplus_fees.billing_amount)
      FROM icplus_fees
      WHERE icplus_fees.charge_id = balance_transactions.source_id
    )
    ELSE NULL
  END AS icplus_fee_total
FROM transfers
JOIN balance_transactions
  ON balance_transactions.automatic_transfer_id = transfers.id
LEFT JOIN charges
  ON charges.balance_transaction_id = balance_transactions.id
WHERE
  date(transfers.date) = date({{PAYOUT_REPORT_DATE}})
</code></pre>
<p>Galtee can also take it a step further and create an itemized row of all IC+ fees to add to each card charge row:</p>
<pre><code class="language-sql">WITH

-- Create a table with a single row of all IC+ fees for each charge
icplus_by_charge AS (
  SELECT
    charge_id,
    SUM(CASE WHEN fee_name = &#x27;interchange&#x27; THEN total_amount END) AS interchange,
    SUM(CASE WHEN fee_name = &#x27;card_scheme&#x27; THEN total_amount END) AS card_scheme,
    SUM(CASE WHEN fee_name = &#x27;volume_fee&#x27; THEN total_amount END) AS volume_fee,
    SUM(CASE WHEN fee_name = &#x27;per_auth_fee&#x27; THEN total_amount END) AS per_auth_fee,
    SUM(total_amount) AS total_icplus_fee
  FROM icplus_fees
  GROUP BY charge_id
)

-- Final query using the IC+ fees by charge table
SELECT
  date(transfers.date) as payout_date,
  transfers.id as payout_id,
  charges.id AS charge_id,
  charges.created AS charge_created_at,
  balance_transactions.id AS balance_transaction_id,
  balance_transactions.source_id,
  balance_transactions.type,
  balance_transactions.net,
  balance_transactions.currency AS settlement_currency,
  balance_transactions.amount AS settlement_amount,
  charges.currency AS presentment_currency,
  charges.amount AS presentment_amount,
  balance_transactions.exchange_rate,
  
  --icplus_fees columns
  icplus_by_charge.total_icplus_fee,
  COALESCE(icplus_by_charge.interchange, 0) AS interchange_fee,
  COALESCE(icplus_by_charge.card_scheme, 0) AS card_scheme_fee,
  COALESCE(icplus_by_charge.volume_fee, 0) AS stripe_volume_fee,
  COALESCE(icplus_by_charge.per_auth_fee, 0) AS stripe_per_auth_fee,
  COALESCE(icplus_by_charge.interchange, 0)
    + COALESCE(icplus_by_charge.card_scheme, 0) AS total_network_fees,
  COALESCE(icplus_by_charge.volume_fee, 0)
    + COALESCE(icplus_by_charge.per_auth_fee, 0) AS total_stripe_fee
FROM transfers
JOIN balance_transactions
  ON balance_transactions.automatic_transfer_id = transfers.id
LEFT JOIN charges
  ON charges.balance_transaction_id = balance_transactions.id
LEFT JOIN icplus_by_charge
  ON icplus_by_charge.charge_id = charges.id
WHERE
  date(transfers.date) = date({{PAYOUT_REPORT_DATE}})
</code></pre>
<p>The final Galtee report output looks like this:</p>























































<table><thead><tr><th>payout_id</th><th>balance_transaction_id</th><th>source_id</th><th>settlement_currency</th><th style="text-align:right">settlement_amount</th><th>presentment_currency</th><th style="text-align:right">presentment_amount</th><th style="text-align:right">exchange_rate</th></tr></thead><tbody><tr><td>po_****</td><td>txn_****</td><td>ch_****</td><td>usd</td><td style="text-align:right">1000</td><td>usd</td><td style="text-align:right">1000</td><td style="text-align:right"></td></tr><tr><td>po_****</td><td>txn_****</td><td>ch_****</td><td>usd</td><td style="text-align:right">1000</td><td>usd</td><td style="text-align:right">1000</td><td style="text-align:right"></td></tr><tr><td>po_****</td><td>txn_****</td><td>ch_****</td><td>usd</td><td style="text-align:right">59</td><td>eur</td><td style="text-align:right">50</td><td style="text-align:right">1.18541</td></tr><tr><td>po_****</td><td>txn_****</td><td>ch_****</td><td>usd</td><td style="text-align:right">1000</td><td>usd</td><td style="text-align:right">1000</td><td style="text-align:right"></td></tr></tbody></table>
<p>And the itemized IC+ fee columns for those same rows:</p>













































<table><thead><tr><th>balance_transaction_id</th><th style="text-align:right">total_icplus_fee</th><th style="text-align:right">interchange_fee</th><th style="text-align:right">card_scheme_fee</th><th style="text-align:right">stripe_volume_fee</th><th style="text-align:right">stripe_per_auth_fee</th></tr></thead><tbody><tr><td>txn_****</td><td style="text-align:right">-21.05</td><td style="text-align:right">-20.5</td><td style="text-align:right">1.95</td><td style="text-align:right">-2.5</td><td style="text-align:right">0</td></tr><tr><td>txn_****</td><td style="text-align:right">-14.95</td><td style="text-align:right">-16</td><td style="text-align:right">3.55</td><td style="text-align:right">-2.5</td><td style="text-align:right">0</td></tr><tr><td>txn_****</td><td style="text-align:right">-2.6005</td><td style="text-align:right">0.15</td><td style="text-align:right">0.303</td><td style="text-align:right">0.1475</td><td style="text-align:right">25</td></tr><tr><td>txn_****</td><td style="text-align:right">-9.55</td><td style="text-align:right">-10.25</td><td style="text-align:right">1.95</td><td style="text-align:right">-1.25</td><td style="text-align:right">0</td></tr></tbody></table>
<p><em>Note: the example above is for illustration of the report format only. Some columns have been hidden for brevity and actual data and amounts have been redacted/modified.</em></p>
<h2>Conclusion</h2>
<p>Financial reporting has historically been a siloed process that relies on push-based files with fixed templates and static schedules. Whether the reports are delivered directly to finance team members or fed into ERP integrations, Stripe’s <a href="https://docs.stripe.com/reports/query-runs">Query Run API</a> makes it possible to build an on-demand, flexible reporting pipeline that can evolve alongside your business.</p>]]></content:encoded></item><item><title><![CDATA[Harbor: Stripe’s AI-assisted prototyping tool]]></title><description><![CDATA[Harbor is the internal tool Stripe teams use to prototype with an AI agent. This post covers how it compiles prototypes in the browser, how the agent inspects its own work, and how comments stay anchored when the page is rewritten.]]></description><link>https://stripe.dev/blog/harbor-stripes-ai-assisted-prototyping-tool</link><guid isPermaLink="true">https://stripe.dev/blog/harbor-stripes-ai-assisted-prototyping-tool</guid><dc:creator><![CDATA[Cristian Rivera]]></dc:creator><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/harbor-stripes-ai-assisted-prototyping-tool/prototype-hero.png"/><link rel="preload" as="image" href="https://stripe.dev/images/harbor-stripes-ai-assisted-prototyping-tool/shipped-hero.png"/><link rel="preload" as="image" href="https://stripe.dev/images/harbor-stripes-ai-assisted-prototyping-tool/builder-monthly-actuals.png"/><p>At Stripe, we’ve integrated AI into many of our workflows. We created <a href="https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents">Minions</a> that write code and built our <a href="https://stripe.dev/blog/meet-stripes-knowledge-ai-platform">Knowledge AI Platform</a> to help Stripes with a wide range of knowledge work. Many tasks are now faster and easier to complete thanks to these AI assistants.</p>
<p>An area where we haven’t seen as many improvements from AI has been prototyping. When making changes to <a href="https://stripe.com/">stripe.com</a>, our team often wants more information to evaluate the feasibility of a new design idea, but can’t always invest in developing a full engineering prototype.</p>
<p>One engineer built the first version of Harbor in about 40 days to solve that problem. We wanted to see whether AI could help designers turn a static design into something dynamic, without needing to understand code.</p>
<p>The result is an interactive, browser-based platform where designers can use AI to rapidly develop shareable prototypes using Stripe’s design systems and realistic mock data.</p>
<p>Harbor immediately improved the design phase of <a href="https://stripe.com/">stripe.com</a> projects, increasing engagement and driving clarity around the final vision. In one of the first projects to use Harbor, a designer built a new product page experience, including its animated hero. Engineers, product managers, and leaders interacted with it in real time, leading to quick iteration and consensus. When the page shipped, the animation carried over nearly one-for-one from the prototype.</p>
<div style="display:flex;flex-wrap:wrap;gap:16px;margin:24px 0 0"><figure style="flex:1 1 260px;margin:0"><figcaption style="font-family:sohne-mono,&#x27;Courier New&#x27;,Courier,monospace;font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--bodyTextColor,#1e1e1e);margin:0 0 8px">Harbor prototype</figcaption><img src="https://stripe.dev/images/harbor-stripes-ai-assisted-prototyping-tool/prototype-hero.png" alt="The hero as prototyped in Harbor: a phone showing an add-funds keypad, over a dashboard, on a violet gradient." style="width:100%;height:auto;display:block"/></figure><figure style="flex:1 1 260px;margin:0"><figcaption style="font-family:sohne-mono,&#x27;Courier New&#x27;,Courier,monospace;font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--bodyTextColor,#1e1e1e);margin:0 0 8px">Shipped stripe.com page</figcaption><img src="https://stripe.dev/images/harbor-stripes-ai-assisted-prototyping-tool/shipped-hero.png" alt="The same hero on the shipped page: a phone over a dashboard, on the same gradient, with the animation carried across." style="width:100%;height:auto;display:block"/></figure></div>
<p>Stripes across the company adapted Harbor for their own needs. Finance teams started presenting monthly actuals and business reviews as clickable prototypes instead of slide decks, a risk operations analyst turned a handle time analysis into a dashboard the rest of the team could inspect, and account executives built pricing proposals, battlecards, and ROI calculators for specific prospects.</p>
<p><img src="https://stripe.dev/images/harbor-stripes-ai-assisted-prototyping-tool/builder-monthly-actuals.png" alt="The Harbor builder: a monthly actuals review prototype rendering on the left, and the chat panel on the right showing the agent reading the KPI row, the revenue chart, and the variance table."/></p>
<blockquote>
<p>A monthly actuals review built in Harbor: the running prototype on the left, the agent and its tool calls on the right.</p>
</blockquote>
<p>Since May 2026, 3,000+ Stripes have created 12,000 prototypes, and 25% of Stripes have created at least one prototype in Harbor. This adoption came with more requests for increased collaboration within a Harbor prototype and interoperability with other AI tooling. Read on to learn how we built this functionality.</p>
<h2>Building Harbor for collaboration with people and agents</h2>
<p>Harbor’s interface is a browser tab with the running prototype and a chat panel. The author selects a design system for the prototype, setting its packages and starting files, then describes what they want. From there, Harbor works like this:</p>
<ul>
<li>The agent writes a multi-file source tree, and the page compiles and renders in the browser rather than on a server.</li>
<li>The agent can read the rendered page, list and inspect elements, click, type, scroll, switch the viewport between mobile, tablet, and desktop widths, and take screenshots.</li>
<li>When the prototype is ready, the author publishes it to a shareable URL. There’s one published version at a time, and every view compiles that copy fresh in the viewer’s own browser.</li>
</ul>
<svg xmlns="http://www.w3.org/2000/svg" id="fig-lifecycle" viewBox="0 0 708 428" style="display:block;width:100%;height:auto;margin:24px 0 0" role="img" aria-labelledby="lc-t lc-d">
<title id="lc-t">Harbor’s render lifecycle: From a prompt to a page running in the browser</title>
<desc id="lc-d">The agent writes a multi-file source tree from the prompt. Drydock compiles it in the browser and returns a running page with a report. The agent reads the page, switches viewport, screenshots, and edits files again. On publish, one snapshot is shared and every viewer’s browser compiles that copy.</desc>
<defs><pattern id="lc-grid" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1" style="fill:var(--artGridDotColor,var(--windowBarText,#1e1e1e))" opacity=".2"></circle></pattern></defs>
<style>
#fig-lifecycle-cap{display:none;margin:8px 0 0}
#fig-lifecycle text{font-family:'sohne-var','Helvetica Neue',Arial,sans-serif;fill:var(--bodyTextColor,#1e1e1e)}
#fig-lifecycle .m{font-family:'sohne-mono',ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:300;letter-spacing:-.012em;font-size:12px;text-transform:uppercase;fill:var(--squareTextColor,#1e1e1e)}
#fig-lifecycle .s10{font-size:10px}#fig-lifecycle .fig{font-size:8px;fill:var(--windowBarText,#1e1e1e)}#fig-lifecycle .bt{fill:var(--windowBarText,#1e1e1e)}
#fig-lifecycle .mute{fill-opacity:.55}#fig-lifecycle .tt{fill:var(--tagTextColor,#1e1e1e)}#fig-lifecycle .lt{fill:var(--invertedTextColor,#1e1e1e)}#fig-lifecycle .light{fill:var(--bodyTextColor,#1e1e1e)}
#fig-lifecycle .h{fill:none;stroke:var(--artStroke,#1e1e1e);stroke-width:1;shape-rendering:crispEdges}
#fig-lifecycle .win{fill:var(--artBackground,transparent);stroke:var(--artStroke,#1e1e1e);stroke-width:1}#fig-lifecycle .frame{fill:var(--windowFrameBG,#dcdcdc)}#fig-lifecycle .ico{fill:var(--windowBarText,#1e1e1e)}
#fig-lifecycle .canvas{stroke:var(--artStroke,#1e1e1e);stroke-width:1}#fig-lifecycle .pane{fill:var(--artForeground,#e8e8e8)}
#fig-lifecycle .tag{fill:var(--artForeground,#e8e8e8);stroke:var(--tagBorderColor,#1e1e1e44);stroke-width:1;stroke-dasharray:1 1;rx:3}#fig-lifecycle .lime{fill:var(--tagHighlightColor,#c4e817)}
#fig-lifecycle .term{fill:var(--codeBG,#f5f5f5);stroke:var(--codeBorderColor,#d5d5d5);stroke-width:1;rx:4}#fig-lifecycle .termf{fill:var(--codeBG,#f5f5f5)}#fig-lifecycle .hc{fill:none;stroke:var(--codeBorderColor,#d5d5d5);stroke-width:1;shape-rendering:crispEdges;vector-effect:non-scaling-stroke}
#fig-lifecycle .pinbg{fill:var(--artForeground,#e8e8e8)}#fig-lifecycle .sq{fill:var(--artStroke,#1e1e1e)}#fig-lifecycle .sql{fill:var(--bodyTextColor,#1e1e1e)}#fig-lifecycle .sqo{fill:none;stroke:var(--artStroke,#1e1e1e);stroke-width:1}#fig-lifecycle .sqol{fill:none;stroke:var(--bodyTextColor,#1e1e1e);stroke-width:1}#fig-lifecycle .sqd{fill:none;stroke:var(--artStroke,#1e1e1e);stroke-width:1;stroke-dasharray:1.5 1.5;stroke-opacity:.6}
#fig-lifecycle .card{fill:var(--artForeground,#e8e8e8);stroke:var(--artStroke,#1e1e1e);stroke-width:1;shape-rendering:crispEdges}
#fig-lifecycle .dash{fill:none;stroke:var(--artStroke,#1e1e1e);stroke-width:1;stroke-dasharray:3 3}
#fig-lifecycle .h,#fig-lifecycle .win,#fig-lifecycle .canvas,#fig-lifecycle .tag,#fig-lifecycle .term,#fig-lifecycle .card,#fig-lifecycle .sqo,#fig-lifecycle .sqol,#fig-lifecycle .sqd,#fig-lifecycle .dash{vector-effect:non-scaling-stroke}
#fig-lifecycle *{animation-duration:30s;animation-iteration-count:infinite;animation-timing-function:cubic-bezier(.2,.8,.2,1);animation-fill-mode:both}
#fig-lifecycle .scene{animation-name:lc-scene}#fig-lifecycle .f2{animation-name:lc-fi2}#fig-lifecycle .f3{animation-name:lc-fi3}#fig-lifecycle .f4{animation-name:lc-fi4}#fig-lifecycle .a3{animation-name:lc-a3}#fig-lifecycle .arrow1{animation-name:lc-a1}#fig-lifecycle .cl1{animation-name:lc-cl1}#fig-lifecycle .cl2{animation-name:lc-cl2}#fig-lifecycle .cl3{animation-name:lc-cl3}#fig-lifecycle .cl4{animation-name:lc-cl4}#fig-lifecycle .blink{animation-name:lc-blink;animation-duration:1s;animation-timing-function:steps(1)}#fig-lifecycle .arrow2{animation-name:lc-a2}#fig-lifecycle .p1{animation-name:lc-pgel1}#fig-lifecycle .p2{animation-name:lc-pgel2}#fig-lifecycle .p3{animation-name:lc-pgel3}#fig-lifecycle .p4{animation-name:lc-pgel4}#fig-lifecycle .rep{animation-name:lc-rep}#fig-lifecycle .insp{animation-name:lc-insp}#fig-lifecycle .loopa{animation-name:lc-loopa}#fig-lifecycle .pub{animation-name:lc-pub}#fig-lifecycle .s1{animation-name:lc-s1}#fig-lifecycle .s2{animation-name:lc-s2}#fig-lifecycle .s3{animation-name:lc-s3}#fig-lifecycle .s4{animation-name:lc-s4}#fig-lifecycle .s5{animation-name:lc-s5}
@keyframes lc-scene{0%{opacity:0}4.44%,94.44%{opacity:1}98.89%,100%{opacity:0}}@keyframes lc-fi2{0%,9.45%{opacity:0;transform:translateX(-6px)}12.22%,100%{opacity:1;transform:none}}@keyframes lc-fi3{0%,12.23%{opacity:0;transform:translateX(-6px)}15.00%,100%{opacity:1;transform:none}}@keyframes lc-fi4{0%,15.00%{opacity:0;transform:translateX(-6px)}17.77%,100%{opacity:1;transform:none}}@keyframes lc-a3{0%,17%{opacity:0}19.44%,100%{opacity:1}}@keyframes lc-a1{0%,22.22%{opacity:0}25%,100%{opacity:1}}@keyframes lc-cl1{0%,30.00%{opacity:0}32.22%,100%{opacity:1}}@keyframes lc-cl2{0%,32.78%{opacity:0}35.00%,100%{opacity:1}}@keyframes lc-cl3{0%,35.56%{opacity:0}37.78%,100%{opacity:1}}@keyframes lc-cl4{0%,38.33%{opacity:0}40.55%,100%{opacity:1}}@keyframes lc-blink{0%,49%{opacity:1}50%,100%{opacity:0}}@keyframes lc-a2{0%,41.11%{opacity:0}43.89%,100%{opacity:1}}@keyframes lc-pgel1{0%,48.33%{opacity:0;transform:translateY(4px)}51.67%,100%{opacity:1;transform:none}}@keyframes lc-pgel2{0%,50.00%{opacity:0;transform:translateY(4px)}53.34%,100%{opacity:1;transform:none}}@keyframes lc-pgel3{0%,51.66%{opacity:0;transform:translateY(4px)}55.00%,100%{opacity:1;transform:none}}@keyframes lc-pgel4{0%,53.33%{opacity:0;transform:translateY(4px)}56.67%,100%{opacity:1;transform:none}}@keyframes lc-rep{0%,55%{opacity:0}57.78%,100%{opacity:1}}@keyframes lc-insp{0%,66.67%{opacity:0}69.44%,100%{opacity:1}}@keyframes lc-loopa{0%,78.89%{opacity:0}81.67%,85.56%{opacity:1}87.78%,100%{opacity:0}}@keyframes lc-pub{0%,85.56%{opacity:0}88.33%,100%{opacity:1}}@keyframes lc-s1{0%,4.44%{opacity:0}6.67%,25%{opacity:1}27.22%,100%{opacity:0}}@keyframes lc-s2{0%,27.22%{opacity:0}29.44%,45%{opacity:1}47.22%,100%{opacity:0}}@keyframes lc-s3{0%,47.22%{opacity:0}49.44%,65.56%{opacity:1}67.78%,100%{opacity:0}}@keyframes lc-s4{0%,67.78%{opacity:0}70%,84.44%{opacity:1}86.67%,100%{opacity:0}}@keyframes lc-s5{0%,86.67%{opacity:0}88.89%,100%{opacity:1}}
@media (prefers-reduced-motion:reduce){#fig-lifecycle *{animation:none !important}#fig-lifecycle .rmh{display:none}}
@media (max-width:600px){#fig-lifecycle .caps,#fig-lifecycle .mob-hide{display:none}#fig-lifecycle-cap{display:block;font-family:'sohne-mono',ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;font-weight:300;letter-spacing:-.012em;text-transform:uppercase;line-height:1.4;color:var(--squareTextColor,#1e1e1e)}}
</style>
<text x="0" y="16" class="m">/ render lifecycle</text>
<path d="M0 24L708 24" class="h"></path>
<g class="scene">
<rect x="0.5" y="36.5" width="707" height="359" class="win"></rect>
<rect x="1" y="37" width="706" height="20" class="frame"></rect>
<path transform="translate(5,41)" d="M11 0H1V1H0V11H1V12H11V11H12V1H11V0ZM11 1V11H1V1H11ZM6 7H10V8H6V7ZM3 7H2V8H3V7ZM4 6V7H3V6H4ZM4 5V6H5V5H4ZM3 4H4V5H3V4ZM3 4V3H2V4H3Z" class="ico" fill-rule="evenodd"></path>
<path transform="translate(691,41)" d="M-8.74228e-08 10L8 10L8 9L11 9L11 10L12 10L12 11L11 11L11 12L8 12L8 11L-4.37113e-08 11L-8.74228e-08 10ZM-3.93402e-07 3L-4.37114e-07 2L8 2L8 1L11 1L11 2L12 2L12 3L11 3L11 4L8 4L8 3L-3.93402e-07 3ZM1 6L-2.62268e-07 6L-2.18557e-07 7L1 7L1 8L4 8L4 7L12 7L12 6L4 6L4 5L1 5L1 6ZM9 2L10 2L10 3L9 3L9 2ZM10 10L9 10L9 11L10 11L10 10ZM2 6L3 6L3 7L2 7L2 6Z" class="ico" fill-rule="evenodd"></path>
<text x="354" y="50.1" class="m fig" text-anchor="middle">[ fig. 1 ]</text>
<rect x="1" y="57" width="4" height="338" class="frame"></rect>
<rect x="703" y="57" width="4" height="338" class="frame"></rect>
<rect x="1" y="391" width="706" height="4" class="frame"></rect>
<rect x="5.5" y="57.5" width="697" height="333" rx="2" fill="url(#lc-grid)" class="canvas"></rect>
<text x="17" y="84" class="m">/ 01 · agent writes files</text>
<path d="M17 92L223 92" class="h"></path>
<rect x="17.5" y="104.5" width="205" height="247" class="win"></rect>
<rect x="18" y="105" width="204" height="20" class="frame"></rect>
<text x="120" y="118.1" class="m fig" text-anchor="middle">chat</text>
<rect x="18" y="125" width="204" height="226" class="pane"></rect>
<text x="251" y="84" class="m">/ 02 · drydock compiles</text>
<path d="M251 92L457 92" class="h"></path>
<rect x="251.5" y="104.5" width="205" height="247" class="win"></rect>
<rect x="252" y="105" width="204" height="20" class="frame"></rect>
<text x="354" y="118.1" class="m fig" text-anchor="middle">drydock</text>
<rect x="252" y="125" width="204" height="226" class="pane"></rect>
<text x="485" y="84" class="m">/ 03 · agent inspects page</text>
<path d="M485 92L691 92" class="h"></path>
<rect x="485.5" y="104.5" width="205" height="247" class="win"></rect>
<rect x="486" y="105" width="204" height="20" class="frame"></rect>
<text x="588" y="118.1" class="m fig" text-anchor="middle">prototype</text>
<rect x="486" y="125" width="204" height="226" class="pane"></rect>
<g class="arrow1"><text x="237" y="232.7" class="m" text-anchor="middle">→</text></g>
<g class="arrow2"><text x="471" y="232.7" class="m" text-anchor="middle">→</text></g>
<rect x="29.5" y="132.5" width="181" height="35" class="tag"></rect><text x="35" y="146" class="m s10 tt">“a pricing page with three</text><text x="35" y="160" class="m s10 tt">plans + a compare button”</text>
<text x="29" y="188" class="m s10 mute">/ source tree · 4 files</text>
<g class="f1"><rect x="29" y="200.5" width="6" height="6" class="sq"></rect><text x="41" y="208" class="m">app.tsx</text></g>
<g class="f2"><rect x="29" y="220.5" width="6" height="6" class="sq"></rect><text x="41" y="228" class="m">plancard.tsx</text></g>
<g class="f3"><rect x="29" y="240.5" width="6" height="6" class="sq"></rect><text x="41" y="248" class="m">plans.json</text></g>
<g class="f4"><rect x="29" y="260.5" width="6" height="6" class="sq"></rect><text x="41" y="268" class="m">styles.css</text></g>
<g class="a3"><rect x="18" y="333" width="204" height="18" class="termf"></rect><path d="M18 333.5H222" class="hc"></path><text x="29" y="345.9" class="m s10 light">&gt; agent · edits app.tsx</text></g>
<text x="263" y="146" class="m s10 mute">in the browser · no server</text>
<rect x="263.5" y="156.5" width="181" height="83" class="term"></rect>
<g class="cl1"><text x="273" y="175" class="m s10 light">&gt; resolve · 4 files</text></g>
<g class="cl2"><text x="273" y="189" class="m s10 light">&gt; compile · tsx → js</text></g>
<g class="cl3"><text x="273" y="203" class="m s10 light">&gt; mount · #root · styles</text></g>
<g class="cl4"><text x="273" y="217" class="m s10 light">&gt; report · no failures</text></g>
<rect class="blink sql" x="273" y="222" width="4" height="8"></rect>
<text x="263" y="264" class="m s10 mute">/ contract</text>
<rect x="263.5" y="272.5" width="171" height="17" class="tag"></rect><text x="269" y="285.7" class="m tt">in · files: react + ts</text>
<rect x="263.5" y="298.5" width="151" height="17" class="tag"></rect><text x="269" y="311.7" class="m tt">out · page + report</text>
<g class="p1"><text x="497" y="148" class="" font-size="14" font-weight="500" letter-spacing="-.02em">Plans</text><path d="M497 154L601 154" class="h"></path></g>
<g class="p2"><rect x="497.5" y="164.5" width="181" height="27" class="card"></rect><text x="505" y="181.9" font-size="10" font-weight="500">Starter</text><text x="671" y="181.9" class="m s10 mute" text-anchor="end">$0</text></g>
<g class="p3"><rect x="497.5" y="200.5" width="181" height="27" class="card"></rect><text x="505" y="217.9" font-size="10" font-weight="500">Team</text><text x="671" y="217.9" class="m s10 mute" text-anchor="end">$99</text></g>
<g class="p4"><rect x="497.5" y="236.5" width="181" height="27" class="card"></rect><text x="505" y="253.9" font-size="10" font-weight="500">Enterprise</text><text x="671" y="253.9" class="m s10 mute" text-anchor="end">custom</text></g>
<g class="p4"><rect x="497.5" y="272.5" width="71" height="15" rx="7.5" class="h"></rect><text x="533" y="283.6" class="" text-anchor="middle" font-size="9">Compare plans</text></g>
<g class="insp"><rect x="497.5" y="296.5" width="135" height="17" class="tag"></rect><text x="503" y="309.7" class="m tt">viewport · 390 px</text></g>
<g class="rep"><rect x="497.5" y="322.5" width="159" height="17" class="tag lime"></rect><text x="503" y="335.7" class="m lt">report · no failures</text></g>
<g class="loopa rmh"><path d="M679 352.5V369.5H443.2M264.8 369.5H29V352.5" class="h"></path><path d="M26 356.5l3-4 3 4" class="h"></path><text x="354" y="372.9" class="m s10 mute" text-anchor="middle">report → the agent iterates</text></g>
<g class="pub"><text x="29" y="373.7" class="m">/ 04 · publish</text><text x="144.416" y="373.7" class="m">→</text><g transform="translate(175,0)"><rect x="0.5" y="360.5" width="163" height="17" class="tag"></rect><rect x="6" y="366" width="6" height="6" class="sq"></rect><text x="18" y="373.7" class="m tt">viewer a · compiles</text></g><g transform="translate(351,0)"><rect x="0.5" y="360.5" width="163" height="17" class="tag"></rect><rect x="6" y="366" width="6" height="6" class="sq"></rect><text x="18" y="373.7" class="m tt">viewer b · compiles</text></g><g transform="translate(527,0)"><rect x="0.5" y="360.5" width="163" height="17" class="tag"></rect><rect x="6" y="366" width="6" height="6" class="sq"></rect><text x="18" y="373.7" class="m tt">viewer c · compiles</text></g></g>
<g class="caps">
<text x="0" y="420" class="m s1 rmh">/ 01 · the agent writes a multi-file source tree from the prompt</text>
<text x="0" y="420" class="m s2 rmh">/ 02 · drydock compiles it in the browser and returns a running page with a report</text>
<text x="0" y="420" class="m s3 rmh">/ 03 · the page renders in the tab; compile and runtime failures land in the report</text>
<text x="0" y="420" class="m s4 rmh">/ 04 · the agent reads the page, switches viewport, screenshots, and edits files again</text>
<text x="0" y="420" class="m s5">/ 05 · on publish, one snapshot is shared; every viewer’s browser compiles that copy fresh</text>
</g></g>
</svg>
<p id="fig-lifecycle-cap" class="text-smallcaps">The agent writes files; Drydock compiles them in the browser; the agent inspects and iterates; viewers compile the copy.</p>
<p>Authors can give permission for others to comment on an element of the page or remix the published prototype into a copy of their own.</p>
<p>As prototypes became inputs to reviews, analyses, and production work, Stripes needed better ways to discuss changing designs and move them between AI tools. In the next sections, we’ll cover how we added commenting in Harbor, and how we integrated Harbor into other AI tooling at Stripe.</p>
<h3>Anchoring comments to changing prototypes</h3>
<p>As more Stripes began using Harbor, they wanted to leave comments. Because Harbor prototypes can change, people and agents need to easily understand comments in their right context. We built comments so they could stay connected to what prompted them, and let people and the agent know when that connection has changed.</p>
<p>Anchoring a comment to a page that the agent keeps rewriting is challenging. Harbor solves this by capturing several signals, including semantic labels, stable identifiers, text evidence, structural paths, list keys, and the surrounding context, such as a dialog or accordion, that needs to be opened to reveal the element. It also records whether a signal was unique at the time. When the agent updates the prototype, the resolution flow uses the stored information to report if the comment’s anchor element is attached, needs review, or is orphaned, rather than silently moving the comment to a plausible replacement.</p>
<svg xmlns="http://www.w3.org/2000/svg" id="fig-anchoring" viewBox="0 0 708 400" style="display:block;width:100%;height:auto;margin:24px 0 0" role="img" aria-labelledby="an-t an-d">
<title id="an-t">How Harbor anchors a reviewer’s comment to a card after the agent rewrites the page</title>
<desc id="an-d">A reviewer pins a comment to the Team plan card. Harbor records several signals for the anchor. The agent rewrites the page: cards move, one changes, one is removed. Each comment re-resolves as attached, needs review, or orphaned.</desc>
<defs><pattern id="an-grid" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1" style="fill:var(--artGridDotColor,var(--windowBarText,#1e1e1e))" opacity=".2"></circle></pattern></defs>
<style>
#fig-anchoring-cap{display:none;margin:8px 0 0}
#fig-anchoring text{font-family:'sohne-var','Helvetica Neue',Arial,sans-serif;fill:var(--bodyTextColor,#1e1e1e)}
#fig-anchoring .m{font-family:'sohne-mono',ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:300;letter-spacing:-.012em;font-size:12px;text-transform:uppercase;fill:var(--squareTextColor,#1e1e1e)}
#fig-anchoring .s10{font-size:10px}#fig-anchoring .fig{font-size:8px;fill:var(--windowBarText,#1e1e1e)}#fig-anchoring .bt{fill:var(--windowBarText,#1e1e1e)}
#fig-anchoring .mute{fill-opacity:.55}#fig-anchoring .tt{fill:var(--tagTextColor,#1e1e1e)}#fig-anchoring .lt{fill:var(--invertedTextColor,#1e1e1e)}#fig-anchoring .light{fill:var(--bodyTextColor,#1e1e1e)}
#fig-anchoring .h{fill:none;stroke:var(--artStroke,#1e1e1e);stroke-width:1;shape-rendering:crispEdges}
#fig-anchoring .win{fill:var(--artBackground,transparent);stroke:var(--artStroke,#1e1e1e);stroke-width:1}#fig-anchoring .frame{fill:var(--windowFrameBG,#dcdcdc)}#fig-anchoring .ico{fill:var(--windowBarText,#1e1e1e)}
#fig-anchoring .canvas{stroke:var(--artStroke,#1e1e1e);stroke-width:1}#fig-anchoring .pane{fill:var(--artForeground,#e8e8e8)}
#fig-anchoring .tag{fill:var(--artForeground,#e8e8e8);stroke:var(--tagBorderColor,#1e1e1e44);stroke-width:1;stroke-dasharray:1 1;rx:3}#fig-anchoring .lime{fill:var(--tagHighlightColor,#c4e817)}
#fig-anchoring .term{fill:var(--codeBG,#f5f5f5);stroke:var(--codeBorderColor,#d5d5d5);stroke-width:1;rx:4}#fig-anchoring .termf{fill:var(--codeBG,#f5f5f5)}#fig-anchoring .hc{fill:none;stroke:var(--codeBorderColor,#d5d5d5);stroke-width:1;shape-rendering:crispEdges;vector-effect:non-scaling-stroke}
#fig-anchoring .pinbg{fill:var(--artForeground,#e8e8e8)}#fig-anchoring .sq{fill:var(--artStroke,#1e1e1e)}#fig-anchoring .sql{fill:var(--bodyTextColor,#1e1e1e)}#fig-anchoring .sqo{fill:none;stroke:var(--artStroke,#1e1e1e);stroke-width:1}#fig-anchoring .sqol{fill:none;stroke:var(--bodyTextColor,#1e1e1e);stroke-width:1}#fig-anchoring .sqd{fill:none;stroke:var(--artStroke,#1e1e1e);stroke-width:1;stroke-dasharray:1.5 1.5;stroke-opacity:.6}
#fig-anchoring .card{fill:var(--artForeground,#e8e8e8);stroke:var(--artStroke,#1e1e1e);stroke-width:1;shape-rendering:crispEdges}
#fig-anchoring .dash{fill:none;stroke:var(--artStroke,#1e1e1e);stroke-width:1;stroke-dasharray:3 3}
#fig-anchoring .h,#fig-anchoring .win,#fig-anchoring .canvas,#fig-anchoring .tag,#fig-anchoring .term,#fig-anchoring .card,#fig-anchoring .sqo,#fig-anchoring .sqol,#fig-anchoring .sqd,#fig-anchoring .dash{vector-effect:non-scaling-stroke}
#fig-anchoring *{animation-duration:30s;animation-iteration-count:infinite;animation-timing-function:cubic-bezier(.2,.8,.2,1);animation-fill-mode:both}
#fig-anchoring .scene{animation-name:an-scene}#fig-anchoring .c1t{animation-name:an-c1t}#fig-anchoring .c1n{animation-name:an-c1n}#fig-anchoring .n2{animation-name:an-c1t}#fig-anchoring .n3{animation-name:an-c1n}#fig-anchoring .c2{animation-name:an-c2}#fig-anchoring .c3{animation-name:an-c3}#fig-anchoring .cn{animation-name:an-cn}#fig-anchoring .flk{animation-name:an-flk}#fig-anchoring .con{animation-name:an-con}#fig-anchoring .blink{animation-name:an-blink;animation-duration:1s;animation-timing-function:steps(1)}#fig-anchoring .cur{animation-name:an-cur}#fig-anchoring .pinA{animation-name:an-pinA}#fig-anchoring .trail{animation-name:an-trail}#fig-anchoring .lead{animation-name:an-lead}#fig-anchoring .pS{animation-name:an-pS}#fig-anchoring .d0{animation-name:an-sig0}#fig-anchoring .d1{animation-name:an-sig1}#fig-anchoring .d2{animation-name:an-sig2}#fig-anchoring .d3{animation-name:an-sig3}#fig-anchoring .d4{animation-name:an-sig4}#fig-anchoring .d5{animation-name:an-sig5}#fig-anchoring .d6{animation-name:an-sig6}#fig-anchoring .pR{animation-name:an-pR}#fig-anchoring .rOk{animation-name:an-rOk}#fig-anchoring .pinB{animation-name:an-pinB}#fig-anchoring .rWarn{animation-name:an-rWarn}#fig-anchoring .pinC{animation-name:an-pinC}#fig-anchoring .rOrph{animation-name:an-rOrph}#fig-anchoring .s1{animation-name:an-s1}#fig-anchoring .s2{animation-name:an-s2}#fig-anchoring .s3{animation-name:an-s3}#fig-anchoring .s4{animation-name:an-s4}
@keyframes an-scene{0%{opacity:0}4.44%,94.44%{opacity:1}98.89%,100%{opacity:0}}@keyframes an-c1t{0%,57.22%{opacity:1}58.89%,100%{opacity:0}}@keyframes an-c1n{0%,57.78%{opacity:0}59.44%,100%{opacity:1}}@keyframes an-c2{0%,55%{transform:translate(0,0)}60%,100%{transform:translate(0,52px)}}@keyframes an-c3{0%,52.22%{opacity:1;transform:translate(0,0)}54.44%,100%{opacity:0;transform:translate(8px,0)}}@keyframes an-cn{0%,57.22%{opacity:0;transform:translate(-8px,0)}60.56%,100%{opacity:1;transform:translate(0,0)}}@keyframes an-flk{0%,50.56%{opacity:0}52.22%,57.78%{opacity:.7}60.00%,100%{opacity:0}}@keyframes an-con{0%,47.78%{opacity:0}49.44%,62.22%{opacity:1}63.89%,100%{opacity:0}}@keyframes an-blink{0%,49%{opacity:1}50%,100%{opacity:0}}@keyframes an-cur{0%,6.67%{opacity:0;transform:translate(440px,380px)}8.33%{opacity:1;transform:translate(440px,380px)}13.89%{opacity:1;transform:translate(260px,200px)}16.67%{opacity:1;transform:translate(260px,200px)}18.89%,100%{opacity:0;transform:translate(260px,200px)}}@keyframes an-pinA{0%,13.89%{opacity:0;transform:translate(260px,200px)}16.11%{opacity:1;transform:translate(260px,200px)}55%{opacity:1;transform:translate(260px,200px)}60%,100%{opacity:1;transform:translate(260px,252px)}}@keyframes an-trail{0%,55%{opacity:0;transform:scaleY(0)}55.01%{opacity:1;transform:scaleY(0)}60%,65.56%{opacity:1;transform:scaleY(1)}68.89%,100%{opacity:0;transform:scaleY(1)}}@keyframes an-lead{0%,16.67%{opacity:0}18.89%,46.67%{opacity:1}49.44%,100%{opacity:0}}@keyframes an-pS{0%,25.56%{opacity:0}27.78%,46.67%{opacity:1}49.44%,100%{opacity:0}}@keyframes an-sig0{0%,27.78%{opacity:0;transform:translateX(-4px)}30.00%,100%{opacity:1;transform:none}}@keyframes an-sig1{0%,29.45%{opacity:0;transform:translateX(-4px)}31.67%,100%{opacity:1;transform:none}}@keyframes an-sig2{0%,31.11%{opacity:0;transform:translateX(-4px)}33.33%,100%{opacity:1;transform:none}}@keyframes an-sig3{0%,32.78%{opacity:0;transform:translateX(-4px)}35.00%,100%{opacity:1;transform:none}}@keyframes an-sig4{0%,34.45%{opacity:0;transform:translateX(-4px)}36.67%,100%{opacity:1;transform:none}}@keyframes an-sig5{0%,36.11%{opacity:0;transform:translateX(-4px)}38.33%,100%{opacity:1;transform:none}}@keyframes an-sig6{0%,37.78%{opacity:0;transform:translateX(-4px)}40.00%,100%{opacity:1;transform:none}}@keyframes an-pR{0%,65.56%{opacity:0}67.78%,100%{opacity:1}}@keyframes an-rOk{0%,68.89%{opacity:0}71.11%,100%{opacity:1}}@keyframes an-pinB{0%,74.44%{opacity:0;transform:translate(260px,148px)}76.67%,100%{opacity:1;transform:translate(260px,148px)}}@keyframes an-rWarn{0%,75.56%{opacity:0}77.78%,100%{opacity:1}}@keyframes an-pinC{0%,82.78%{opacity:0;transform:translate(260px,284px)}85.00%{opacity:1;transform:translate(260px,284px)}88.33%,100%{opacity:.6;transform:translate(260px,284px)}}@keyframes an-rOrph{0%,81.11%{opacity:0}83.33%,100%{opacity:1}}@keyframes an-s1{0%,4.44%{opacity:0}6.67%,23.33%{opacity:1}25.56%,100%{opacity:0}}@keyframes an-s2{0%,25.56%{opacity:0}27.78%,46.67%{opacity:1}48.89%,100%{opacity:0}}@keyframes an-s3{0%,47.78%{opacity:0}50.00%,63.33%{opacity:1}65.56%,100%{opacity:0}}@keyframes an-s4{0%,65.56%{opacity:0}67.78%,100%{opacity:1}}
@media (prefers-reduced-motion:reduce){#fig-anchoring *{animation:none !important}#fig-anchoring .rmh{display:none}#fig-anchoring .c2{transform:translate(0,52px)}#fig-anchoring .rms{display:inline}}
@media (max-width:600px){#fig-anchoring .caps,#fig-anchoring .mob-hide{display:none}#fig-anchoring-cap{display:block;font-family:'sohne-mono',ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;font-weight:300;letter-spacing:-.012em;text-transform:uppercase;line-height:1.4;color:var(--squareTextColor,#1e1e1e)}}
</style>
<text x="0" y="16" class="m">/ comment anchoring</text>
<path d="M0 24L708 24" class="h"></path>
<g class="scene">
<rect x="0.5" y="36.5" width="707" height="331" class="win"></rect>
<rect x="1" y="37" width="706" height="20" class="frame"></rect>
<path transform="translate(5,41)" d="M11 0H1V1H0V11H1V12H11V11H12V1H11V0ZM11 1V11H1V1H11ZM6 7H10V8H6V7ZM3 7H2V8H3V7ZM4 6V7H3V6H4ZM4 5V6H5V5H4ZM3 4H4V5H3V4ZM3 4V3H2V4H3Z" class="ico" fill-rule="evenodd"></path>
<path transform="translate(691,41)" d="M-8.74228e-08 10L8 10L8 9L11 9L11 10L12 10L12 11L11 11L11 12L8 12L8 11L-4.37113e-08 11L-8.74228e-08 10ZM-3.93402e-07 3L-4.37114e-07 2L8 2L8 1L11 1L11 2L12 2L12 3L11 3L11 4L8 4L8 3L-3.93402e-07 3ZM1 6L-2.62268e-07 6L-2.18557e-07 7L1 7L1 8L4 8L4 7L12 7L12 6L4 6L4 5L1 5L1 6ZM9 2L10 2L10 3L9 3L9 2ZM10 10L9 10L9 11L10 11L10 10ZM2 6L3 6L3 7L2 7L2 6Z" class="ico" fill-rule="evenodd"></path>
<text x="354" y="50.1" class="m fig" text-anchor="middle">[ fig. 2 ]</text>
<rect x="1" y="57" width="4" height="310" class="frame"></rect>
<rect x="703" y="57" width="4" height="310" class="frame"></rect>
<rect x="1" y="363" width="706" height="4" class="frame"></rect>
<rect x="5.5" y="57.5" width="697" height="305" rx="2" fill="url(#an-grid)" class="canvas"></rect>
<rect x="17.5" y="69.5" width="474" height="282" class="win"></rect>
<rect x="18" y="70" width="473" height="20" class="frame"></rect>
<text x="254.5" y="83.1" class="m fig" text-anchor="middle">prototype · published</text>
<rect x="18" y="90" width="473" height="261" class="pane"></rect>
<text x="29" y="114" class="" font-size="20" font-weight="500" letter-spacing="-.02em">Plans</text>
<path d="M29 120L219 120" class="h"></path>
<g><rect x="29.5" y="128.5" width="450" height="39" class="card"></rect><text x="41" y="145" font-size="14" font-weight="500"><tspan class="c1t">Starter</tspan><tspan class="c1n" x="41">Starter Plus</tspan></text><text x="41" y="159" class="m s10 mute">individuals · $0 / month</text><text x="468" y="145" class="m mute" text-anchor="end">01</text></g>
<g class="c2"><rect x="29.5" y="180.5" width="450" height="39" class="card"></rect><text x="41" y="197" font-size="14" font-weight="500">Team</text><text x="41" y="211" class="m s10 mute">growing teams · $99 / month</text><text x="468" y="197" text-anchor="end" class="m mute"><tspan class="n2 rmh">02</tspan><tspan class="n3" x="468">03</tspan></text></g>
<g class="c3 rmh"><rect x="29.5" y="232.5" width="450" height="39" class="card"></rect><text x="41" y="249" font-size="14" font-weight="500">Enterprise</text><text x="41" y="263" class="m s10 mute">custom · sso · dedicated support</text><text x="468" y="249" class="m mute" text-anchor="end">03</text></g>
<g class="cn"><rect x="29.5" y="180.5" width="450" height="39" class="card"></rect><text x="41" y="197" font-size="14" font-weight="500">Growth</text><text x="41" y="211" class="m s10 mute">small teams · $49 / month</text><text x="468" y="197" class="m mute" text-anchor="end">02</text></g>
<g class="rOrph"><text x="29" y="288" class="m s10 mute">enterprise · removed by the rewrite</text></g>
<rect x="29.5" y="300.5" width="95" height="19" rx="9.5" class="h"></rect><text x="77" y="313.9" class="" text-anchor="middle" font-size="10">Compare plans</text>
<rect x="25.5" y="124.5" width="458" height="151" class="dash flk rmh"></rect>
<g class="con rmh"><rect x="18" y="333" width="473" height="18" class="termf"></rect><path d="M18 333.5H491" class="hc"></path><text x="29" y="345.9" class="m s10 light">&gt; agent · rewriting the page</text><rect class="blink light sql" x="205" y="337" width="4" height="10"></rect></g>
<g class="cur rmh"><path d="M0 0v12l3-3h5z" class="sq"></path></g>
<g class="trail rmh" style="transform-origin:260px 206px"><path d="M260 206V246" class="h" stroke-dasharray="2 2"></path></g>
<g class="pinA" transform="translate(260,252.0)"><rect x="-6" y="-6" width="12" height="12" class="pinbg"></rect><rect x="-5.5" y="-5.5" width="11" height="11" class="sqo"></rect><rect x="-3" y="-3" width="6" height="6" class="sq"></rect></g>
<g class="pinB" transform="translate(260,148.0)"><rect x="-6" y="-6" width="12" height="12" class="pinbg"></rect><rect x="-5.5" y="-5.5" width="11" height="11" class="sqo"></rect><rect x="-2.5" y="-2.5" width="5" height="5" class="sqo"></rect></g>
<g class="pinC" transform="translate(260,284)"><rect x="-6" y="-6" width="12" height="12" class="pinbg"></rect><rect x="-5.5" y="-5.5" width="11" height="11" class="sqd"></rect></g>
<g class="panel">
<g class="pR"><text x="504" y="84" class="m">/ resolution</text><path d="M504 92L691 92" class="h"></path></g>
<g class="pS rmh"><text x="504" y="84" class="m">/ signals recorded</text><path d="M504 92L691 92" class="h"></path>
<g class="lead rmh"><rect x="504.5" y="104.5" width="179" height="17" class="tag"></rect><rect x="510" y="110" width="6" height="6" class="sq"></rect><text x="522" y="117.7" class="m tt">comment · “price ok?”</text></g>
<g class="sig d0 rmh"><rect x="504.5" y="134.5" width="171" height="17" class="tag"></rect><rect x="510" y="140" width="6" height="6" class="sq"></rect><text x="522" y="147.7" class="m tt">label · card:pricing</text></g>
<g class="sig d1 rmh"><rect x="504.5" y="160.5" width="127" height="17" class="tag"></rect><rect x="510" y="166" width="6" height="6" class="sq"></rect><text x="522" y="173.7" class="m tt">id · plan-team</text></g>
<g class="sig d2 rmh"><rect x="504.5" y="186.5" width="119" height="17" class="tag"></rect><rect x="510" y="192" width="6" height="6" class="sq"></rect><text x="522" y="199.7" class="m tt">text · “team”</text></g>
<g class="sig d3 rmh"><rect x="504.5" y="212.5" width="171" height="17" class="tag"></rect><rect x="510.5" y="218.5" width="5" height="5" class="sqo"></rect><text x="522" y="225.7" class="m tt">path · main/ul/li[2]</text></g>
<g class="sig d4 rmh"><rect x="504.5" y="238.5" width="135" height="17" class="tag"></rect><rect x="510" y="244" width="6" height="6" class="sq"></rect><text x="522" y="251.7" class="m tt">list key · team</text></g>
<g class="sig d5 rmh"><rect x="504.5" y="264.5" width="119" height="17" class="tag"></rect><rect x="510.5" y="270.5" width="5" height="5" class="sqo"></rect><text x="522" y="277.7" class="m tt">reveal · none</text></g>
<g class="sig d6 rmh"><rect x="504" y="297" width="6" height="6" class="sq"></rect><text x="516" y="303.9" class="m s10 mute">unique</text><rect x="568.5" y="297.5" width="5" height="5" class="sqo"></rect><text x="580" y="303.9" class="m s10 mute">not unique</text></g></g>
<g class="rWarn"><rect x="504.5" y="139.5" width="175" height="17" class="term"></rect><rect x="510.5" y="145.5" width="5" height="5" class="sqol"></rect><text x="522" y="152.7" class="m light">review · text changed</text></g>
<g class="rOk"><rect x="504.5" y="243.5" width="175" height="17" class="tag lime"></rect><rect x="510" y="249" width="6" height="6" class="sq"></rect><text x="522" y="256.7" class="m lt">attached · card moved</text></g>
<g class="rOrph"><rect x="504.5" y="279.5" width="155" height="17" class="tag"></rect><rect x="510.5" y="285.5" width="5" height="5" class="sqd"></rect><text x="522" y="292.7" class="m tt">orphaned · removed</text></g>
</g>
<g class="caps">
<text x="0" y="392" class="m s1 rmh">/ 01 · a reviewer pins a comment to the team card</text>
<text x="0" y="392" class="m s2 rmh">/ 02 · harbor records several signals for the anchor, and whether each was unique</text>
<text x="0" y="392" class="m s3 rmh">/ 03 · the agent rewrites the page: cards move, one changes, one is removed</text>
<text x="0" y="392" class="m s4">/ 04 · comments re-resolve: attached, needs review, or orphaned. nothing is silently moved</text>
</g></g>
</svg>
<p id="fig-anchoring-cap" class="text-smallcaps">A comment is pinned; the agent rewrites the page; it re-resolves: attached, needs review, or orphaned.</p>
<p>Published comments are tied to the version of the prototype where they were posted. When the author publishes a new snapshot, Harbor marks existing comments as stale so reviewers can tell which version the feedback is for.</p>
<h3>Integrating Harbor with other AI tools</h3>
<p>Harbor’s in-browser experience worked well for designers, but Stripes working in our other AI tools wanted to access it from there, so they could bring interactive visual prototypes into existing workflows. Two architectural choices made this possible: exposing Harbor’s toolset over our internal Model Context Protocol (MCP) server, and exposing Drydock, Harbor’s rendering engine, as its own service.</p>
<p>Harbor exposes a toolset through Stripe’s MCP infrastructure that allows other agents to create, read, update, and publish a prototype without needing a person to manually copy context into a new conversation. The same toolset is available to coding agents working on Stripe devboxes. They can pull a Harbor prototype’s file tree into a real repository, or push an idea from another workflow into Harbor for visual exploration. The handoff preserves the decisions around ownership and implementation while eliminating manual transcription between tools.</p>
<p>Drydock, Harbor’s rendering engine, has a small contract: it takes a multi-file React and TypeScript file tree and returns a running page inside the browser, alongside a report that includes compile and runtime failures, so that the agent can inspect and interact with the running result. Because that contract doesn’t depend on anything else in Harbor, other tools across Stripe can use it. This was particularly useful in Kai, our AI platform for knowledge work. Harbor gave Kai users the means to take the next step with a prototype. One of our producers, who run stripe.com programs across design, engineering, and editorial, ran a viewport analysis of <a href="https://stripe.com/">stripe.com</a> in Kai, then used Harbor to turn the results into a clickable prototype.</p>
<h2>What we learned</h2>
<p>Harbor began as an experiment in building with AI. Now it gives all Stripes a way to rapidly experiment and iterate on visual prototypes. We started with solving a small, team-focused problem and expanded beyond its original boundary thanks to our <a href="https://mikebifulco.com/posts/how-stripe-uses-friction-logs">culture of friction logging</a>, AI-forward culture, and colleagues who were willing to try out the early version of a tool, share what they made, and help develop useful pieces into shared infrastructure.</p>
<p>In Stripe’s internal AI tools, we’ve found that it’s effective to keep agents specialized (on research, coding, or browser-native tools) while exposing them to capabilities in other systems. In our viewport analysis example, we started with research in Kai, and built an interactive prototype in Harbor. In other examples, engineers mocked up internal admin tools in Harbor, then handed the generated prototype to a coding agent to rebuild for production. And Harbor’s own rendering engine became infrastructure for Kai.</p>
<h2>What’s next</h2>
<p>Our goal is to continue to make Stripe’s AI tools work together rather than have each tool solve every part of the workflow. For Harbor, we want prototypes to hold small amounts of state and connect to live data. That includes prototype-scoped databases and embeddable widgets that refresh internal data queries on their own, instead of requiring an agent to copy numbers into the source and rewrite them whenever the data changes.</p>
<p>We’re still early in understanding what becomes possible when research, code, and interactive artifacts can move between AI tools without losing their context. There’s a lot left to build, and the work spans infrastructure, developer tools, product systems, and AI all at once. If that’s the kind of problem you want to work on, <a href="https://grnh.se/b5wzjpge1us">we’re hiring</a>.</p>]]></content:encoded></item><item><title><![CDATA[What if Stripe could work the way your business does]]></title><description><![CDATA[Discover how Stripe Scripts, Workflows, Custom Objects, and UI extensions help businesses customize logic, data, and operations directly within Stripe.]]></description><link>https://stripe.dev/blog/what-if-stripe-could-work-the-way-your-business-does</link><guid isPermaLink="true">https://stripe.dev/blog/what-if-stripe-could-work-the-way-your-business-does</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/what-if-stripe-could-work-the-way-your-business-does/image2.png"/><link rel="preload" as="image" href="https://stripe.dev/images/what-if-stripe-could-work-the-way-your-business-does/image1.png"/><link rel="preload" as="image" href="https://stripe.dev/images/what-if-stripe-could-work-the-way-your-business-does/image3.png"/><p>Stripe provides the financial infrastructure businesses need to accept payments, manage subscriptions, send invoices, and move money. For many businesses, Stripe’s default behavior is exactly what they need - it works without any additional customization.</p>
<p>Sooner or later, you need something specific: a custom proration rule, a multi-step fraud response, data that doesn’t fit an existing Stripe object, or a purpose-built Dashboard view for your operations team.</p>
<p>Traditionally, that meant building around Stripe. A webhook sends an event to your server, your application runs some logic, and then calls the Stripe API. It’s a flexible pattern, but every customization adds more infrastructure, and more responsibility for availability, retries, state, secrets, security, and error handling.</p>
<p><a href="https://docs.stripe.com/extensibility">Stripe extensibility</a> lets you move more of that customization into Stripe. In this post, you’ll learn how to extend Stripe across three dimensions: logic, data, and user interface, using <a href="https://docs.stripe.com/billing/scripts">scripts</a>, <a href="https://docs.stripe.com/workflows">Workflows</a>, <a href="https://docs.stripe.com/custom-objects">Custom Objects</a>, and <a href="https://docs.stripe.com/stripe-apps/build-ui">UI extensions</a>, and see how they work together in a complete example.</p>
<p>Prefer to watch? See how these capabilities work together in our Stripe Sessions talk, <a href="https://www.youtube.com/watch?v=1v5ik1t0azc">Extensibility with Scripts and Workflows</a>.</p>
<h2><strong>Three dimensions of extensibility</strong></h2>
<p>Most Stripe customizations fall into three dimensions:</p>
<ul>
<li><strong>Logic</strong> defines what should happen when something occurs in your business.</li>
<li><strong>Data</strong> represents the concepts and relationships that are unique to your business.</li>
<li><strong>User interface</strong> gives your team the tools it needs to view that data and take action.</li>
</ul>
<p>You can use each independently, but they become much more powerful when they work together. Your interface can display your custom data, your data can contain business-specific actions, and those actions can invoke your logic.</p>
<h2><strong>Logic: From default behavior to custom rules</strong></h2>
<p>The best place to begin is usually Stripe’s default behavior. If the default meets your needs, there is nothing else to build or maintain. When you need something different, configuration gives you additional options supplied by Stripe or a partner. You can change how something behaves without writing the logic yourself.</p>
<p>Some rules, however, belong only to your business, and that is where custom logic fits. For example, when a customer changes plans midway through a billing period, Stripe can calculate the credit for the old plan and the charge for the new one down to the second - this is called proration. That works for most businesses. Yours might round to the nearest day, waive adjustments below $5, or use an entirely different calculation.</p>
<p>With a <strong>script</strong>, you can write that rule in TypeScript and deploy it to Stripe. When Stripe reaches the proration decision in its billing flow, it invokes your code, receives an answer, and continues processing. The code runs in a secure environment on Stripe, without requiring a server for you to manage. Scripts are for singular decisions: Stripe asks a question and your code returns an answer.</p>
<p><img src="https://stripe.dev/images/what-if-stripe-could-work-the-way-your-business-does/image2.png" alt="Stripe Scripts"/></p>
<p>More complex requirements need a different process. For example, when an early fraud warning arrives, you might need to check the charge amount, issue a refund when it falls below a threshold, and notify your risk team in Slack.</p>
<p><strong>Stripe Workflows</strong> coordinates these steps in the correct order. Workflows can be triggered by a Stripe event, started by someone in the Dashboard, or called from your backend. It can combine Stripe API calls, third-party integrations, and custom actions without requiring you to build the surrounding orchestration infrastructure.</p>
<p>Scripts run code. Workflows coordinate work.</p>
<h2><strong>Data: Teach Stripe the language of your business</strong></h2>
<p>Everything in Stripe is an object. Customers, Products, Payments, Invoices, and Subscriptions are all objects that represent the core concepts Stripe understands. But your business likely has other concepts that do not fit neatly into those objects. Imagine a vehicle rental company called Rocket Rides. A Driver maps naturally to a Stripe Customer, but the company also needs to represent Vehicles and Rentals.</p>
<p>A Vehicle has a make, model, year, and vehicle identification number. A Rental has a start date, status, assigned vehicle, and driver. These are structured entities with validation rules and relationships, not simply additional labels.</p>
<p>You could store some of this information in metadata, but metadata is not a complete data model. It does not provide typed fields, enforce a schema, or express relationships between objects.</p>
<p><strong>Custom objects</strong> let you define these business-specific models inside Stripe. Rocket Rides can require every Vehicle to have a 17-character VIN and define a Rental that references both a Vehicle custom object and Stripe’s native Customer object.</p>
<p>In simplified TypeScript, that relationship looks like this:</p>
<pre><code class="language-typescript">interface RentalFields {
  startsAt: Date
  status: &#x27;upcoming&#x27; | &#x27;in_progress&#x27; | &#x27;completed&#x27;
  vehicle: Ref&lt;Vehicle&gt;
  driver: Ref&lt;Customer&gt;
}

@CustomObject
class Rental extends BaseObject&lt;RentalFields&gt; {}
</code></pre>
<p>Once deployed, Stripe provides APIs for creating, reading, updating, and deleting these objects. Your business-specific data now has structure, relationships, and the same platform foundation as native Stripe data.</p>
<p><img src="https://stripe.dev/images/what-if-stripe-could-work-the-way-your-business-does/image1.png" alt="Custom objects"/></p>
<h2><strong>User interface: Build where your team already works</strong></h2>
<p>APIs give developers access to your custom data, but operations and finance teams should not need to query an endpoint to understand what is happening.</p>
<p>With <strong>UI extensions</strong>, you can create pages and side panels inside the Stripe Dashboard using Stripe’s design system. You choose where the experience appears and which information it presents.</p>
<p>For Rocket Rides, that could be a rental operations page containing the vehicle details, driver, payment history, rental status, and inspection information. Instead of switching between Stripe and a separate internal tool, the operations team gets a single view designed around the way it works. Viewing the data is only part of the job. When a vehicle is returned, the team also needs to end the rental.</p>
<p><img src="https://stripe.dev/images/what-if-stripe-could-work-the-way-your-business-does/image3.png" alt="Custom UI"/></p>
<h2><strong>Bringing logic, data, and UI together</strong></h2>
<p>Rocket Rides can add an <strong>End Rental</strong> button to its custom Dashboard page. That button invokes an action defined on the Rental custom object.</p>
<p>When an operator selects it, the action updates the rental status, triggers the inspection workflow, and marks the vehicle as available again. The team remains in the Dashboard while custom logic runs against custom data and coordinates the next stage of the process.</p>
<p>This is where the three dimensions of extensibility become one connected system:</p>
<ul>
<li>Custom objects model Rentals and Vehicles.</li>
<li>A custom Dashboard page gives the operations team the right view.</li>
<li>An action provides a business-specific operation.</li>
<li>A workflow coordinates everything that must happen next.</li>
</ul>
<p>The team clicks one button, but behind it Stripe is operating in the language of the business.</p>
<h2><strong>Start with what you need</strong></h2>
<p>Extensibility does not mean every Stripe integration needs custom code. Start with the default. Use configuration when an existing option meets your requirements. Build custom logic, data, or interfaces when the requirement is genuinely unique to your business.</p>
<p>Scripts let you define individual decisions. Workflows coordinate processes over time. Custom objects represent the concepts that Stripe does not model natively. UI extensions give your team tailored pages, side panels, and actions inside the Dashboard.</p>
<p>Together, they let you extend Stripe without turning every customization into another standalone system your team must operate.</p>
<p>Stripe provides the foundation. Extensibility lets you build the business only you can build.</p>
<p>Start building with <a href="https://docs.stripe.com/extensibility">Stripe extensibility</a>.</p>]]></content:encoded></item><item><title><![CDATA[AI didn't write our SDK. It changed how we built it.]]></title><description><![CDATA[AI was involved at every stage of building Stripe's extensibility SDK, but the story isn't that it wrote code faster. Here's how prototypes, agent-drafted specs, and persistent guidance got ideas into reviewable shape sooner.]]></description><link>https://stripe.dev/blog/ai-didnt-write-our-sdk-it-changed-how-we-built-it</link><guid isPermaLink="true">https://stripe.dev/blog/ai-didnt-write-our-sdk-it-changed-how-we-built-it</guid><dc:creator><![CDATA[Mike North]]></dc:creator><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>When we shipped Stripe&#x27;s <a href="https://docs.stripe.com/stripe-apps">extensibility SDK</a> this year, AI was involved at every stage. But the story people expect - &quot;AI wrote a lot of code and we shipped faster&quot; - isn&#x27;t the interesting one.</p>
<p>The interesting part is what AI did to the stages that usually slow platform work down: the gap between an idea and a working prototype, between a prototype and a reviewable spec, between a spec and aligned implementation, and between implementation and polished deliverable.</p>
<p>AI compressed all of those gaps - not by replacing human judgment, but by making human judgment more effective by giving it better artifacts to operate on, earlier in the process. That changed the quality of our decisions, not just the speed of our output.</p>
<h2>Platform work has an abstraction problem</h2>
<p>When you&#x27;re building an SDK, you&#x27;re not building a feature a user clicks on. You&#x27;re building primitives other developers will compose. The hard questions aren&#x27;t &quot;can we build this?&quot; but &quot;should we build it this way?&quot;</p>
<p>What should the developer experience feel like? Where should boundaries live? What belongs in the model and what&#x27;s intentionally outside it? How do you keep six product surfaces coherent when each team could reasonably optimize in a different direction?</p>
<p>In a traditional cycle, you discuss the idea, write a design doc, debate feasibility in the abstract, eventually build a prototype, discover rough edges weeks later, and course-correct. Each stage is expensive, so you do fewer of them, sequentially.</p>
<p>That sequential cost is the real bottleneck in platform work, not the typing speed. The bottleneck is how long it takes to get from idea to an artifact the whole team can evaluate.</p>
<h2>A working prototype in two days</h2>
<p>Early on, we were shaping how developers would define custom objects and attach behavior to them - part product design, part architecture, part platform strategy. We weren&#x27;t asking &quot;can we add methods to <a href="https://docs.stripe.com/custom-objects">custom objects</a>?&quot; We were asking what the authoring experience should feel like, how much of the existing platform we could reuse, and whether the approach was sustainable as part of Stripe&#x27;s broader extensibility architecture.</p>
<p>Without AI, this would have been a two-to-three-week effort of stubbing out the type system, building the transform pipeline, wiring up the demo, and writing the docs. The kind of work that&#x27;s well-understood but time-consuming enough that you&#x27;d normally skip the prototype entirely and go straight to a design doc.</p>
<p>With an AI coding assistant, one engineer on the team built a working prototype in roughly two days - complete with a demo and documentation site.</p>
<p>That prototype wasn&#x27;t the production code we shipped. But it was conceptually close to the production direction: author-defined custom objects, methods attached to those objects, generated runtime machinery, and a model that reused existing platform pieces rather than creating a bespoke system.</p>
<p>The value wasn&#x27;t that one person built something fast. It was that the team&#x27;s conversation shifted from speculation to evaluation. Without the prototype, we&#x27;d have been debating a design document, and design documents require the suspension of disbelief. People have to imagine the developer experience, imagine the runtime behavior, imagine whether the pieces fit together. A working prototype removed that gap. The discussion moved from &quot;Could this work?&quot; to &quot;Is this the right shape?&quot; and &quot;Where should the boundaries live?&quot;</p>
<p>That prototype ultimately convinced us to reprioritize the feature into scope for our next major launch. The decision happened weeks earlier than it would have otherwise, because we had a concrete artifact to evaluate rather than a speculative proposal to debate.</p>
<h2>AI as architecture collaborator</h2>
<p>The next challenge was turning prototype intuition into coherent architecture.</p>
<p>Prototypes create momentum, but platform work needs clean boundaries and durable abstractions. AI became useful differently here: not writing implementation, but helping us explore and articulate the implications of design choices across multiple surfaces.</p>
<p>Much of the architectural work was sharpening important distinctions:</p>
<ul>
<li>Product code shouldn&#x27;t know or care how an extension is implemented</li>
<li>Low-latency enforcement belongs in deterministic declarative artifacts, not user-authored functions on every write path</li>
<li>A state machine is a lifecycle artifact; expressions are predicates inside that lifecycle</li>
<li>Dashboard-authored and code-authored experiences should wrap the same primitive, not fork into incompatible languages</li>
</ul>
<p>AI didn&#x27;t invent those distinctions (the team&#x27;s judgment was the source of truth) but AI helped us write down implications quickly enough to test whether our abstractions held across scripts, workflows, custom objects, SDK generation, runtime enforcement, and developer tooling.</p>
<p>Platform work is especially vulnerable to local optimization. Any individual surface can be made to work alone. The system only becomes coherent if the same abstractions hold everywhere. AI helped us keep more of the system in view at once.</p>
<h2>Spec-first agent delegation</h2>
<p>As the architecture solidified, we identified work naturally suited for delegation: bounded scope, clear correctness criteria, well-known patterns, strong connection to developer-facing behavior.</p>
<p>ESLint rules were a good example: they encode what&#x27;s allowed in the authoring experience, what&#x27;s unsafe, and what should fail early. But we didn&#x27;t hand an agent a vague goal and hope for the best. That&#x27;s the common failure mode with AI coding, and it produces work that looks plausible while quietly violating deeper constraints.</p>
<p>Instead, the workflow became disciplined:</p>
<ol>
<li>Identify work bounded enough for agent delegation</li>
<li>Ask the agent to draft a detailed spec</li>
<li>Review those specs as GitHub issues before implementation begins</li>
<li>Implement using agent skills grounded in domain-specific guidance</li>
<li>Review output against architectural intent</li>
</ol>
<p>Here&#x27;s what one of those specs looked like in practice. A GitHub issue for a rule that prevents developers from using bare numbers in Custom Object fields (they should use Integer or Decimal instead):</p>
<pre><code class="language-markdown">## Rule Contract

- Rule name: co/no-bare-number
- Purpose: Disallow bare `number` in Custom Object fields, requiring Integer or Decimal instead.
- Spec reference: S-002 Custom Object DDL §4, §3.7, §12.1
- Severity: error
- Auto-fix: yes
- Requires type-checker: yes

## Required Behavior

Applies only to *.object.ts files. Targets type positions within the fields type — the type argument F passed to BaseObject&lt;F&gt;. The type-checker is required to resolve aliases and nested references.

## Invalid Examples

interface OrderFields {
  total: number;     // error
  discount?: number; // error
}

type MyNum = number;

interface OrderFields {
  count: MyNum;      // error (resolved through alias)
}

## Valid Examples

interface OrderFields {
  total: Integer;
  rate: Decimal;
}

## Auto-fix behavior

* Offer two auto-fix options: update the field to use the `Integer` or `Decimal` types from the extensibility-sdk
</code></pre>
<p>The spec defines exactly what &quot;correct&quot; means: which files it applies to, what it catches, what it doesn&#x27;t, whether it auto-fixes. The agent drafts this, the team reviews and refines it, and only then does implementation begin. The review catches design issues such as &quot;should this also fire inside action parameter types?&quot; that would have been discovered much later in a code review-only workflow.</p>
<p>As this pattern scaled, we used it to produce roughly twenty ESLint rules that enforce the SDK&#x27;s authoring constraints - sandbox boundaries, import restrictions, type safety requirements. The spec-review-implement cycle actually caught design issues earlier than a conventional &quot;just write it&quot; approach would have, because the spec review forced us to articulate what &quot;correct&quot; meant before anyone touched implementation.</p>
<h2>Persistent guidance, not one-off prompts</h2>
<p>Complex platform work can&#x27;t rely on one-off prompting. Agents need context that persists: product principles, implementation constraints, architectural invariants, pitfalls specific to your system.</p>
<p>We invested in repository-local documentation written for AI consumption - architectural principles, boundary definitions, known anti-patterns. When agents made changes, the output was more likely to be globally consistent with platform intent, not just locally correct in isolation.</p>
<p>If the architecture says build-time validation is authoritative, the agent shouldn&#x27;t defer a check until deploy. If the product model says expressions are deterministic, the implementation must reject loopholes that let side-effecting code into the evaluator path. Making those invariants available at the point of work raised the floor of what arrived for human review.</p>
<p>This also solved a collaboration problem. When multiple teams contribute to a shared codebase, onboarding cost is usually high. Persistent AI guidance reduced it. Engineers from adjacent teams could make architecturally consistent changes without weeks of context-building. The documentation that helped agents helped humans too.</p>
<h2>Compressing the feedback loop</h2>
<p>Near the end of the project, AI shifted to high-throughput refinement. As feedback came in from demos, design reviews, and hands-on use, we could turn around changes same-day that would normally take two to three days each. AI helped explore edge cases, reproduce subtle bugs, update documentation, adjust examples, generate targeted tests, and keep multiple artifacts consistent while details were still changing.</p>
<p>The final mile of platform work is where quality is won or lost. The architecture might be sound, but the deliverable only lands if examples are clear, demos are believable, docs match implementation, and rough edges are caught before users find them.</p>
<p>This is usually the phase where quality gets triaged away. Every piece of feedback competes with remaining implementation work, and most of it loses. AI changed that calculus. The marginal cost of responding to feedback dropped enough that we could incorporate most of it before the deadline rather than deferring to the next quarter. The deliverable shipped tighter than it would have otherwise.</p>
<h2>What AI didn&#x27;t do</h2>
<p>AI did not replace architectural judgment. It didn&#x27;t decide which abstractions were load-bearing. It didn&#x27;t know which tradeoffs mattered most for Stripe&#x27;s platform. It didn&#x27;t automatically understand that two things similar in code represent fundamentally different product concepts.</p>
<p>In several places, raw output needed correction. AI could blur boundaries that needed to stay sharp. It could generate implementations that looked locally reasonable while quietly violating a deeper invariant. It could make plausible architectural claims that were subtly wrong. The more AI amplified output volume, the more important review discipline became — which is why the spec-first delegation model mattered so much.</p>
<p>The lesson is not that AI can independently drive complex platform work. It&#x27;s that AI dramatically increases the surface area over which a strong team can apply judgment. The judgment still has to be there. AI makes it go further.</p>
<h2>What this means for how we work</h2>
<p>AI changes the economics of making ideas concrete.</p>
<p>Before, a working prototype, a detailed architecture brief, a full set of implementation specs, agent-readable guidance, and a tight test-and-refine loop would each have competed for scarce engineering time. Because they were expensive, you&#x27;d choose fewer of them - which meant more abstract discussion, later feedback, and less confidence early on.</p>
<p>With AI, the cost dropped enough that we could use more of them, earlier, across more surfaces. Instead of asking stakeholders to trust a document, we showed a prototype. Instead of asking engineers to infer intent, we wrote detailed specs. Instead of asking agents to guess constraints, we gave them reviewed issues and persistent guidance. Instead of accepting late feedback as too expensive, we folded it in.</p>
<p>That&#x27;s the shift. Not &quot;AI writes code faster&quot; but &quot;AI makes more of the development loop concrete earlier.&quot; When ideas become artifacts sooner, teams make better decisions, catch problems earlier, and build with more confidence.</p>
<p>For platform work - where the hardest part is getting a cross-functional team to agree to a shared conviction about the right abstractions - that&#x27;s exactly the kind of leverage that compounds.</p>
<p>We&#x27;re still early in understanding what this means for how engineering teams should be organized and how projects should be structured. But the direction is clear: the teams that benefit most from AI won&#x27;t be the ones that use it to type faster. They&#x27;ll be the ones that redesign their workflows around the new economics of making ideas real.</p>]]></content:encoded></item><item><title><![CDATA[Building a data plane from scratch: Stripe’s own high-performance distributed proxy]]></title><description><![CDATA[To minimize failed payments for our users, Stripe targets 99.9995% reliability.  As traffic grew beyond 1.6% of global GDP, this put intense strain on the service mesh.  This is the story of the infrastructure team replacing Envoy, a popular solution, with an alternative architecture.]]></description><link>https://stripe.dev/blog/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy</link><guid isPermaLink="true">https://stripe.dev/blog/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy</guid><dc:creator><![CDATA[Jack Loughran]]></dc:creator><pubDate>Wed, 26 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy/image3.png"/><link rel="preload" as="image" href="https://stripe.dev/images/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy/image1.png"/><link rel="preload" as="image" href="https://stripe.dev/images/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy/image4.png"/><link rel="preload" as="image" href="https://stripe.dev/images/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy/image2.png"/><p>A failed payment request means a Stripe user did all the work to get a customer to pay, but lost the sale because of us. We never want this to happen. To minimize failed payments for our users, we target 99.9995% reliability. Maintaining that level of reliability while processing <a href="https://stripe.com/newsroom/news/stripe-2025-update">around 1.6% of global GDP</a> is an exceptionally difficult technical challenge, but most of us who join Stripe’s infrastructure teams are here because we love technical challenges.</p>
<p>For almost 10 years, we used Envoy as our proxy for Stripe’s service mesh. It worked well for us for years, but as Stripe’s traffic grew and we increased our reliability targets, we started running into critical issues that made us wonder if we needed our own proxy.</p>
<p>We decided to try building a replacement for Envoy. We wanted better reliability and scalability, but we weren’t sure if we’d be able to match Envoy’s latency or CPU footprint. This was a technical risk: if our solution ended up slower or less efficient than Envoy, it would prevent us from deploying it.</p>
<h2>Why did we build it ourselves?</h2>
<p>Internal services at Stripe use our service mesh to communicate with each other. The data plane of the service mesh is responsible for request routing, load balancing, authorization, encryption, and other similar features.</p>
<p>Stripe’s service mesh handles millions of requests per second between thousands of services. One public API request relies on many internal service requests, so small improvements in data-plane latency or reliability make a huge difference in maintaining 99.9995% public API uptime.</p>
<p>Over time, a few reliability and latency issues we had with Envoy became large problems we could no longer ignore. For example, under consistent traffic, <a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/intro/threading_model">Envoy’s worker-based threading model</a> means that each worker will usually hold a connection to every upstream endpoint it can route traffic to. The default deployment strategy is to run Envoy with one worker per CPU core, which means each server machine ends up with one outbound connection per client core.</p>
<p>Here is an example of what this would look like with two 8-core client machines load balancing sustained traffic between two server machines:</p>
<p><img src="https://stripe.dev/images/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy/image3.png" alt=""/></p>
<p>Every worker on both client machines maintains a connection to both server machines, which means each server machine needs to handle 16 connections for just these two clients. This becomes especially problematic if you run an infrastructure service that handles requests from the entire fleet (e.g., a service that processes application tracing data). That infrastructure service is forced to maintain connections for roughly every CPU core fleetwide, which is many hundreds of thousands of connections. Scaling up a machine enough to handle this many connections is expensive, and often the problem went unnoticed until too many open connections overloaded the service and caused an incident.</p>
<p>Over time, our Envoy rollouts also started to get increasingly risky, which forced us to roll out upgrades more slowly. <a href="https://www.hyrumslaw.com/">Hyrum&#x27;s Law</a> is especially applicable at Stripe because our fleet contains many unique services. This means small behavior changes in Envoy updates would occasionally cause problems for us.</p>
<p>For example, in 2024, we ran into an especially scary issue when trying to roll out an Envoy upgrade. This upgrade pulled in a new version of nghttp2, which <a href="https://nghttp2.org/blog/2023/10/10/nghttp2-v1-57-0/">introduced a rapid-reset HTTP/2 DDoS defense</a> to protect against a vulnerability. Unfortunately, when using the default settings, this mitigation accidentally rejected some legitimate traffic to one of our services. Luckily, we caught this in a QA load test and fixed the configuration before rolling out to production, but this was a close call. If our load test didn’t replicate the specific traffic pattern the service receives in production, we might not have noticed this regression until it started rejecting real requests. A single production incident like this could jeopardize our 99.9995% reliability goal for the whole year.</p>
<p>Running Envoy also limited our ability to provide custom features for Stripe. For example, Stripe’s Ruby services require big, expensive machines to run. We had designed a new routing approach we could use to distribute traffic more efficiently across backend machines to reduce our Ruby machine sizes and make our Ruby backends more reliable. But this would have required too much customization of Envoy, so we never pursued it.</p>
<h2>What we built</h2>
<p>Mesh-proxy is our Go proxy and Envoy replacement. To efficiently implement all the features of a proxy, mesh-proxy has its own implementation of HTTP/1.1 and HTTP/2. Mesh-proxy connects to our service registry directly for service discovery and supports retries, hedging, rate limiting, and dynamic priority-based request routing.</p>
<p>Go has a few features that helped make mesh-proxy successful. Goroutines are cheap to spin up, which allows us to spin up new ones for every connection. <a href="https://pkg.go.dev/golang.org/x/net/http2">Go’s official HTTP/2 framing library</a> was very useful, and Go is well-supported at Stripe, so we benefit from great developer tooling.</p>
<p>Our biggest risk in building mesh-proxy was that we weren’t sure if we’d be able to match Envoy’s latency or CPU footprint, but our early load tests looked good compared to our Envoy deployment. After rolling out mesh-proxy, we saw around half the CPU under high load, and roughly a 50% latency reduction compared to our Envoy deployment. At our scale, this saves around 3,000 days of cumulative inter-service latency each day.</p>
<p>To address the connection cardinality problem, we built mesh-proxy with a different concurrency model. Instead of a worker-based model with one worker per core, mesh-proxy uses one read goroutine and one write goroutine per connection. Because mesh-proxy multiplexes requests across a shared connection rather than partitioning requests across many workers, we only need one connection per host, compared to one connection per worker with Envoy.</p>
<p>Here is what the 8-core client machines from above look like after migrating to mesh-proxy:</p>
<p><img src="https://stripe.dev/images/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy/image1.png" alt=""/></p>
<p>Each client machine only needs one connection to each server machine, which means the server machines only need to maintain two connections for these two clients. This connection reduction means fleetwide services don’t need to maintain nearly as many connections, which allows them to run reliably on smaller machines. The downside is that this approach required more work to coordinate shared request state across goroutines, but we felt this tradeoff was worth it.</p>
<p>We also found that connecting to our service registry directly from mesh-proxy was more efficient than xDS in many cases. Our Envoy deployment tended to cause more xDS updates than were necessary, which used extra CPU and memory on every machine. Here are a couple of graphs of the most significant example of a service that previously received an enormous number of xDS updates. You can tell when mesh-proxy was rolled out by the massive drop in CPU and memory.</p>
<p><img src="https://stripe.dev/images/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy/image4.png" alt=""/></p>
<p><img src="https://stripe.dev/images/building-a-data-plane-from-scratch-stripes-own-high-performance-distributed-proxy/image2.png" alt=""/></p>
<p>In addition to the performance improvements, rolling out mesh-proxy enabled us to make improvements that were not previously feasible for us with Envoy. We rolled out a feature that uses custom Ruby infrastructure code to balance traffic more evenly between Ruby backend replicas, which gives us 15–20% more headroom before traffic bursts overwhelm the machines. We were also able to roll out an overhaul of our priority-based routing system that has enabled improvements to our deployment infrastructure, rate limiting system, and traffic shifting logic. These features and others on our roadmap were far too custom for us to realistically build into our old Envoy deployment.</p>
<h2>What’s next</h2>
<p>It’s a little counterintuitive that building something in-house would be more reliable than operating a battle-tested open-source project. But our internal service mesh has become complex and custom enough that we’ve had great success building our own proxy. All mesh-proxy code is reviewed by our team and rigorously tested with unit tests, end-to-end tests, and synthetic tests before deployment.</p>
<p>Because our team operates components that handle communication between almost every service at Stripe, we have a unique advantage for identifying inefficiencies and finding solutions. We’re currently working on capabilities like anomaly detection and smart routing so we can find and fix issues before users feel any impact.</p>
<p>If you’re interested in improving infrastructure at Stripe, <a href="https://stripe.com/jobs">we’re hiring</a>.</p>]]></content:encoded></item><item><title><![CDATA[Reading the signals: Helping your AI-built payment integration understand business intent]]></title><description><![CDATA[The low-level code that makes a payment go through is getting cheap, fast. What is becoming valuable is the judgment above it: knowing what the money is supposed to do and making sure the code obeys it.]]></description><link>https://stripe.dev/blog/helping-ai-built-payment-integration-understand-business-intent</link><guid isPermaLink="true">https://stripe.dev/blog/helping-ai-built-payment-integration-understand-business-intent</guid><dc:creator><![CDATA[David Benchi]]></dc:creator><pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Watching an agent build a payment integration is impressive. It is fast, and building has become almost easy. A few prompts, and the <code>PaymentIntent</code> is created, the Payment Element renders, the flow runs end to end. The demo passes.</p>
<p>That speed is what developers tell me constantly now: building on Stripe has never been this quick. And they are right. The next step is helping the integration understand what the money is supposed to do, not just how to move it.</p>
<p>I am the Technical Advisor to the Head of Professional Services EMEA, and reviewing integrations for large users is most of my job, so I see where the gaps are. Someone builds an integration, it looks correct, and most of it is. Then they come to us saying it’s not working as expected, even though on paper it should be. The code ran, but the result did not match the business intent behind it.</p>
<p>When the business intent is missing, nothing throws and nothing is logged. The integration simply starts sending signals, money rests in states it should not rest in, and a week later a support ticket lands: &quot;I was charged and got nothing.&quot; The code was correct. The business intent behind it had not been made explicit yet. The good news is that these signals are readable, and once you can read them, you can get ahead of them.</p>
<h2>Why payments are different</h2>
<p>Payments are different from most software a team builds. The developers usually have strong development skills, but not always payment skills. They work from the API reference and build against it the same way they would build anything else.</p>
<p>Two things are usually missing at once: the payment knowledge, and the business intent behind the money. Put those gaps together and you get code with correct syntax that passes tests, but then behaves in ways nobody decided on in production. It does not crash. It moves money along paths the business never chose. The model was never the constraint. Knowing what the money is supposed to do is the part worthy of your attention, and the part you can add.</p>
<p>That knowledge rarely announces itself as an error. Instead the integration emits signals, small clues in the data that something is off. Each looks minor and unrelated on its own. Read together, against what the money was supposed to do, they point at a single source, the part a developer cannot see without payment knowledge. That is where a review begins, and it is exactly what you can teach the integration to check for itself.</p>
<p>This post walks you through how to read those signals, encode the business intent, and validate every build against it, whether the build came from a person or an agent.</p>
<h2>The PaymentIntent and the business intent</h2>
<p>When I start an integration review, I ask for the first fifteen minutes with no code, just the business: what should happen to the money, and what must never happen to it. If I read the code first, I get biased and start reasoning from code to code. I would rather build the end-to-end business version in my head, find the payment pattern that fits it, and only then look at how it becomes a <code>PaymentIntent</code>.</p>
<p>That order matters, so enforce  it within your team: define the business intent before the payment intent. The two share a word for a reason. The <code>PaymentIntent</code> is the Stripe object that tracks the payment through its lifecycle. The business intent is the set of decisions behind it, the rules the money has to obey. An agent is very good at producing the first. It cannot know the second unless someone tells it.</p>
<p>Here is what that gap looks like in code. A <code>return_url</code> is required for any redirect-based payment method, so on its own it tells you nothing. The signal is a flow that redirects every customer away, even card payments that never needed a redirect:</p>
<pre><code class="language-javascript">// Client. redirect: &quot;always&quot; bounces every customer to the return page,
// even card payments that never needed a redirect.
// redirect: &quot;if_required&quot; would keep those on the page. Why always? And what runs there?
const { error } = await stripe.confirmPayment({
  elements,
  confirmParams: {
    return_url: &quot;https://example.com/order/complete&quot;,
  },
  redirect: &quot;always&quot;,
});
</code></pre>
<p>The syntax is correct. But the object cannot tell you why every payment leaves the page. Is fulfillment meant to happen on that return page? Was redirecting everyone a deliberate business decision, or just the default nobody questioned? The business intent behind it is nowhere in the code, and neither the object nor the model can enforce a rule it was never given.</p>
<h2>Signals that the business intent is missing</h2>
<p>You have probably seen at least one of these. On their own they look like small, unrelated bugs in different parts of the stack. Put them next to the business intent and they turn out to be the same failure wearing five faces.</p>
<p><strong>An authorization rate of 43% that was really above 90%.</strong> A retry loop was resubmitting hard declines, <code>authentication_required</code>, <code>incorrect_number</code>, or <code>expired_card</code>, none of which passes on a second attempt. It looks like a processor problem. The cause is retry logic that treats every decline the same instead of branching on the <code>decline_code</code>.</p>
<p><strong><code>PaymentIntents</code> resting in <code>requires_action</code>.</strong> A server-side confirm with no client-side <code>handleNextAction</code>, so the 3DS step is never triggered and the intent waits for an authentication nobody sent. Money that could have been collected is instead stuck, and nothing errors.</p>
<p><strong>A <code>requires_capture</code> with money held and no order behind it.</strong> The order is created by a client call that runs after the authorization lands. Kill the tab in that window and the money is held with nothing behind it. The customer was charged and got nothing, and the exception tracker is empty.</p>
<p><strong>Connected accounts that quietly went <code>restricted</code>.</strong> No subscription to <code>account.updated</code>, so an account hits <code>requirements.currently_due</code>, flips to <code>restricted</code>, and keeps taking payments into <code>testmode_charges_only</code>. Live volume is lost while the code that moves money keeps working.</p>
<p><strong>Async payment methods refunded like cards.</strong> The integration fulfills off the return URL, ignores <code>payment_intent.processing</code>, and never handles the <code>succeeded</code> that lands a day or two later. Revenue you already earned, refunded.</p>
<p>Independent signals like these tend to arrive late, after the money has already moved. Five subsystems, five different bugs. None threw an error. None is a syntax problem. Each one is a decision about how the money should behave that nobody actually made. On their own they look unrelated, but read against the business intent they share one source: the payment knowledge that was never written into the code. Once you can read them, you can encode them.</p>
<h2>Encoding the business intent</h2>
<p>This part is harder, because the right answer depends on the team, and there is no single best practice. Think about how you already treat code style: a formatter and a linter keep the syntax consistent so anyone can read it. Business intent needs the same discipline, and most teams do not have it yet. Whether you use a well-named function, a comment that states the rule, a pull request linked to a documented requirement, metadata on the object, or a Stripe custom object does not matter. The point is that the decision is written down where the next person, or the next agent, cannot miss it.</p>
<p>Here is the test I use. I ask a developer why a parameter is set the way it is, and I want more than the technical meaning. I want the business needs behind it. Why does every payment redirect? Why do we retry, and which declines? What happens when a connected account becomes restricted? If the answer is &quot;that is just how it was generated,&quot; the intent was never encoded.</p>
<p>Metadata is a good habit here, because it makes the intent legible from the outside. Open a <code>PaymentIntent</code> in the Dashboard, and if the metadata says which order and decision it belongs to, you understand the business behind it without reading the code:</p>
<pre><code class="language-javascript">// Server. Put the business context on the object itself.
const order = await orders.createPending(); // your order id, status &quot;pending&quot;

const paymentIntent = await stripe.paymentIntents.create({
  amount,
  currency,
  // Business decision: authorize now, capture only once the order is confirmed.
  // Never hold funds we cannot fulfill.
  capture_method: &quot;manual&quot;,
  // Business decision: every authorization must trace back to an order.
  metadata: { order_id: order.id },
});
</code></pre>
<h2>Validating every build against the intent</h2>
<p>We have an agent to build the code. We need an agent to validate it.</p>
<p>The best teams I work with have stopped treating the build agent as the whole story. They run a small system of agents. An orchestrator coordinates the work for the development team. A builder agent writes the code. And a verifier agent runs after the build, with one job: check that what was built actually does what the money is supposed to do.</p>
<p>The verifier is where it comes together. To build one, someone has to encode the business knowledge into it. You cannot check that a terminal decline is never retried, or that no order exists without an authorization behind it, unless someone states those rules. So the verifier forces the business intent to be written down. In most reviews the business owner never stated it up front, which is exactly why nothing encoded it. Building a verifier forces the conversation that should have happened at the start.</p>
<p>The important part is that those rules are code, not a checklist. Here are three of the five signals from earlier, each written down as something a verifier can run on every build. The other two follow the same shape:</p>
<pre><code class="language-javascript">// Verifier rules, each one a signal that cost a live integration money.
const paymentRules = [
  {
    rule: &quot;Never retry a terminal decline&quot;,
    check: (retry) =&gt;
      ![&quot;authentication_required&quot;, &quot;incorrect_number&quot;, &quot;expired_card&quot;]
        .includes(retry.declineCode),
  },
  {
    rule: &quot;A held authorization must have an order behind it&quot;,
    check: (pi) =&gt;
      pi.capture_method !== &quot;manual&quot; || Boolean(pi.metadata?.order_id),
  },
  {
    rule: &quot;Async payment methods handle their asynchronous result&quot;,
    check: (integration) =&gt;
      integration.subscribesTo(&quot;payment_intent.processing&quot;) &amp;&amp;
      integration.subscribesTo(&quot;payment_intent.succeeded&quot;),
  },
];
</code></pre>
<p>How these rules run is up to the team, and both common paths work. You can encode them in a skill the verifier agent loads before it reviews a build, so the check happens conversationally as part of the agent workflow. Or you can wire the same rules into automated testing, a CI step that inspects the integration and fails the build when a rule is violated, the same way a linter fails on a style break. The teams getting the most value do both: the skill catches intent gaps while the code is being written, and the test suite enforces the same rules on every merge so nothing regresses later. What matters is not the mechanism but that the rules run on every build automatically, not when someone remembers to look.</p>
<p>None of these came from a specification. Each one came from an integration that looked correct and lost money until someone read the signal and wrote the rule down. That is the point: the rules live where the code can be checked against them, and writing them forces the conversation with the business owner that should have happened first.</p>
<p>And that is the shift: judgment stops being personal and becomes shared. A business team encodes what correct means into the verifier, and every build, whoever or whatever wrote it, is checked against the same rules. None of this removes the human. Encoding the knowledge into an agent does not hand over the decision, it makes it explicit and repeatable. The person still owns what correct means; the agents only enforce it.</p>
<h2>Conclusion</h2>
<p>The value is moving. The low-level code that makes a payment go through is getting cheap, fast. What is becoming valuable is the judgment above it: knowing what the money is supposed to do and making sure the code obeys it.</p>
<p>So read the signals your integration is already sending. State the business intent out loud, encode it where the code can be checked against it, and keep a human owning what correct means. Implementing the <code>PaymentIntent</code> is getting cheap. Being right about the business intent behind it is now the whole job.</p>]]></content:encoded></item><item><title><![CDATA[From charging a blended rate to IC++: pass network costs to your connected accounts]]></title><description><![CDATA[SaaS platforms don't fit a single mold when it comes to payments pricing. An early-stage vertical software company needs simple, flexible tools to experiment and iterate. A platform moving upmarket needs granular cost control and the ability to offer sophisticated pricing to enterprise customers. And as AI features reshape how platforms monetize, that diversity is only going to increase.]]></description><link>https://stripe.dev/blog/pass-network-costs-to-your-connected-accounts</link><guid isPermaLink="true">https://stripe.dev/blog/pass-network-costs-to-your-connected-accounts</guid><dc:creator><![CDATA[Safia El Bayed]]></dc:creator><pubDate>Mon, 10 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/pass-network-costs-to-your-connected-accounts/image1.png"/><p>SaaS platforms don&#x27;t fit a single mold when it comes to payments pricing. An early-stage vertical software company needs simple, flexible tools to experiment and iterate. A platform moving upmarket needs granular cost control and the ability to offer sophisticated pricing to enterprise customers. And as AI features reshape how platforms monetize, that diversity is only going to increase.</p>
<p>Connect has made significant strides on both ends of that spectrum. For platforms that want flexibility without complexity, segmented pricing lets you set and manage rates directly from the Dashboard with the ability to differentiate by payment method, geography, and other attributes. For platforms moving upmarket, Interchange Plus Plus (IC++) takes things further: it gives you the ability to offer cost-plus pricing to your users, passing network costs transparently while protecting your margins, without the operational burden of managing rate tables, navigating messy reconciliation, or tracking constant interchange updates from the networks.</p>
<p>That last capability is proving to be a meaningful unlock. In the article below, we dig into how network cost passthrough actually works: what it means for your pricing model, how to set it up and how it shows up in your users&#x27; fee reporting.</p>
<h2>Why blended pricing hurts platform margins</h2>
<p>On a blended rate, the platform absorbs all interchange variance. When your card mix is predictable, this isn’t a problem. But premium rewards cards, corporate purchasing cards, and high-interchange consumer cards can cost materially more in network fees than your blended rate assumes. The difference comes straight out of your spread, and as platforms scale and attract larger, more sophisticated merchants, that card mix tends to shift in the higher cost direction.</p>
<p>Network cost passthrough solves this by moving the platform to IC++: interchange and card scheme fees flow directly to connected accounts at cost, and the platform earns a clean, protected margin on top. Regardless of card mix, the economics become predictable. The operational headache of tracking interchange schedules, managing rate table updates, or reconciling cost variances disappears. And for connected accounts, the model is actually more transparent: they receive reporting on the network costs attributed to their payment, not a blended approximation of it.</p>
<p>That combination: margin protection for the platform and cost transparency for users, is why IC++ is increasingly the default choice for platforms moving upmarket.</p>
<h2>Setting the stage: IC+ vs IC++</h2>
<p>IC+ and IC++ are related but distinct offerings for Connect platforms:</p>
<ul>
<li>IC+ (Interchange Plus): A pricing model where the platform is charged the underlying network costs (interchange + scheme fees) plus a Stripe markup, instead of a single blended rate.</li>
<li>IC++ (also referred to as network cost passthrough for platforms): A Connect capability built on top of IC+ that lets a platform pass its network costs through to its connected accounts. In this case, the platform can offer IC+pricing to its own connected accounts rather than charging them a blended rate.</li>
</ul>
<h2>How network cost passthrough (IC++) works</h2>
<h3>Eligibility requirements</h3>
<p>For your platform to be eligible for network cost passthrough, the following must be true:</p>
<ul>
<li>Your platform must be on IC+ pricing</li>
<li>Your platform must control pricing (IC++ is only relevant if the platform is on a buy-rate, not on revenue share)</li>
<li>The connected account must have the transfers capability enabled</li>
<li>The connected account should be the Merchant of Record of the transaction: in practice, this means you’re either using Direct Charges or Destination Charges with the <code>on_behalf_of</code> parameter fund flows</li>
<li>The platform and connected account pair must be in eligible geographies (you can work with your account team to ensure geographical availability)</li>
</ul>
<h3>Enable network cost passthrough for your connected accounts</h3>
<p>You can enable Network Cost Passthrough for a connected account either on the Dashboard or through the Stripe API.</p>
<p>In the Dashboard, navigate to the connected account&#x27;s detail page. In the monetization section, toggle on &quot;Pass through network costs&quot; in the Network costs tab.</p>
<p>Through the API, create a Pricing Config Scheme of type <code>network_costs</code> for the connected account using the Stripe-Account header:</p>
<pre><code class="language-bash">curl POST https://api.stripe.com/v1/pricing_configs/network_costs/schemes \
  -u &quot;sk_live_xxxx:&quot; \
  -H &quot;Stripe-Version: 2026-06-24.preview&quot; \
  -H &quot;Stripe-Account: CONNECTED_ACCOUNT_ID&quot; \
  -d enabled=true
</code></pre>
<p>To check the current status for a connected account:</p>
<pre><code class="language-bash">curl https://api.stripe.com/v1/pricing_configs/network_costs/schemes/current_at \
  -u &quot;sk_live_xxxx:&quot; \
  -H &quot;Stripe-Version: 2026-06-24.preview&quot; \
  -H &quot;Stripe-Account: CONNECTED_ACCOUNT_ID&quot;
</code></pre>
<p>Once enabled, IC++ passes eligible card-network costs, including interchange, scheme fees, and applicable adjustments, through to the connected account at cost. It does not pass through separate Stripe product fees.</p>
<p>In practice, Stripe first charges network costs to your platform account on your IC+ billing schedule (typically within two days of the payment occuring). Within a few hours, we recover these costs from the connected account’s balance and add them to the platform’s balance via a Balance Transaction of type <code>platform_fee_transfer</code>. Note that if the connected account has paid out its funds, issued refunds, or otherwise reduced its balance before the recovery occurs, the network cost recovery can take the account negative. In this case, payouts are paused until the connected account’s balance goes positive again.</p>
<h3>Adjust the pricing for your connected accounts</h3>
<p>To accommodate this change, your platform’s existing blended application fee, which was priced to absorb network costs, needs to come down.</p>
<p>If you use Stripe&#x27;s <a href="https://docs.corp.stripe.com/connect/platform-pricing-tools">Platform Pricing Tool</a> (no-code pricing management), the recommended approach is to create a dedicated pricing group for IC++ accounts rather than modifying your existing rules. Add a condition of &quot;Network cost passthrough = Enabled&quot; to scope the rule correctly, then set a lower volume fee that reflects your Stripe fees and your markup, without the network costs.</p>
<p>Alternatively, if you manage pricing programmatically, you can update your own internal pricing engine to accommodate for this change.</p>
<p><img src="https://stripe.dev/images/pass-network-costs-to-your-connected-accounts/image1.png" alt="Blended and passthrough pricing diagrams"/></p>
<h2>Reporting and reconciliation for your merchants</h2>
<p>As you change your pricing strategy for your connected accounts, you must accompany this change by providing them with new reporting capabilities. You can help your connected accounts understand their network costs by sharing the following reports with them:</p>
<ul>
<li>IC+ plan-level report: This report shows data aggregated at the calendar month level. This helps connected accounts understand the monthly network costs computed across all their charges. It provides interchange plan names and an aggregated scheme fee total</li>
<li>IC+ transaction-level report: This report helps connected accounts understand transaction costs at an individual-transaction level. It provides information about which charges led to higher network costs and how refunds and disputes impact network costs. This report shows data aggregated by calendar month.</li>
</ul>
<p>Both reports provide complementary information. You can access the reports for a connected account in several ways:</p>
<ul>
<li>Stripe Dashboard (for connected accounts with Dashboard access): this route requires no technical lift at all from you as a platform.</li>
<li>Network cost passthrough embedded component: you can use Connect embedded components to add connected account dashboard functionality to your website. These libraries allow you to grant your users access to Stripe products directly in your dashboard. This route requires very little technical lift from you as a platform.</li>
<li><a href="https://docs.stripe.com/api/reporting/report_run">Reporting API</a>: our Reporting API lets you programmatically access the same financial data that&#x27;s in your Dashboard’s financial reports. This route requires some technical lift from you as a platform.</li>
<li>Sigma or Stripe Data Pipeline (for platforms with access to these two products):  to run custom analyses and integrate network cost data into your existing workflows, you can access this report data in Sigma and Stripe Data Pipeline. Similarly, this route requires some technical lift from you as a platform.</li>
</ul>
<p>Regardless of how you access the report, the network cost schema is the same and is described <a href="https://docs.stripe.com/connect/network-cost-passthrough-platforms?core-dashboard-or-api=dashboard#network-cost-report-schema">here in our documentation</a>.</p>
<h2>How to deploy this new pricing strategy</h2>
<h3>Start with a pilot cohort</h3>
<p>Enabling IC++ is a pricing change with real financial implications for your connected accounts. We recommend starting with a pilot cohort of a few connected accounts that are good candidates:</p>
<ul>
<li>High-volume, payment-aware accounts: merchants who already think carefully about their processing costs and will understand the change quickly</li>
<li>Commercially engaged: accounts you have a close relationship with and can get feedback from</li>
</ul>
<h3>Communicate before you enable</h3>
<p>Connected accounts need context before they see new line items on their statements. Before enabling this change for a cohort, send a short explanation covering: what is changing, why (transparency and cost accuracy), what their new fee structure will look like, and where the connected accounts can find the IC++ reports.</p>
<h3>Embed IC++ into your go-to-market motion</h3>
<p>Once the pilot validates your rollout approach, IC++ becomes a commercial differentiator in addition to being an operational change. For prospecting, IC++ is a key value proposition with enterprise and payment-aware buyers who have outgrown blended pricing elsewhere. Position IC++ as a premium tier with greater transparency for sophisticated operators.</p>
<h2>Get started</h2>
<p>Network cost passthrough is available for eligible Connect platforms today. If you&#x27;re ready to move your connected accounts to IC++, the <a href="https://docs.stripe.com/connect/network-cost-passthrough-platforms">full documentation</a> covers everything from enabling passthrough per account to reading the network cost reports.</p>
<p>If you&#x27;re not yet enabled for IC++ at the platform level, or you want to talk through whether this pricing model is the right fit for your business, reach out to your Stripe account team. They can confirm your eligibility and help you build a rollout plan.</p>]]></content:encoded></item><item><title><![CDATA[Agentic payments for marketplaces: How a smart fridge can shop safely]]></title><description><![CDATA[While a smart fridge can detect when a household is running out of milk, the more interesting engineering problem begins after that.]]></description><link>https://stripe.dev/blog/agentic-payments-for-marketplaces</link><guid isPermaLink="true">https://stripe.dev/blog/agentic-payments-for-marketplaces</guid><dc:creator><![CDATA[Ana Andres]]></dc:creator><pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/agentic-payments-for-marketplaces/image2.png"/><link rel="preload" as="image" href="https://stripe.dev/images/agentic-payments-for-marketplaces/image1.png"/><p>How does the fridge turn a sensor signal into a trusted grocery order? How does it find an available product, choose a delivery window, and pay on the household’s behalf, without receiving unrestricted access to the household’s card or wallet?</p>
<p>For the fictional grocery marketplace GreenMart, the first architecture choice is not about payment. It is about how GreenMart becomes discoverable to agents. GreenMart could build and operate its own ACP product feed, discovery document, and checkout implementation. Instead, it uses the <a href="https://stripe.com/blog/agentic-commerce-suite">Stripe Agentic Commerce Suite</a> to upload or connect its catalog and expose a dedicated, hosted ACP endpoint.</p>
<p>That choice lets GreenMart share current product, price, and availability data with AI agents without building and maintaining a protocol-specific public surface itself.</p>
<p>The solution includes:</p>
<ul>
<li>Stripe <a href="https://docs.stripe.com/agentic-commerce/for-sellers">Agentic Commerce Suite</a> to turn GreenMart’s existing catalog and commerce data into a hosted ACP storefront. Stripe can also expose UDP storefront and eventually manage any changes to protocols.</li>
<li><a href="https://docs.stripe.com/agentic-commerce/acp">Agentic Commerce Protocol</a> (ACP) for the fridge buying agent and the companion mobile app to interact with that storefront.</li>
<li>Household authorization policies to define what the shopping agent may buy.</li>
<li>A household payment authorization experience—for example, a fridge companion app—that decides when the agent may use household payment authority.</li>
<li><a href="https://link.com/">Link for AI wallet</a>  for agents as an optional way to give the shopping agent bounded payment capability without exposing a card to the fridge or marketplace.</li>
<li><a href="https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens">Shared Payment Tokens</a> (SPTs) as the underlying Stripe payment primitive for direct Stripe integrations; Link can handle those mechanics for the agent.</li>
</ul>
<p>We follow this core principle: an agent should be able to make approved decisions autonomously, but it should not receive more authority than the buyer intended.</p>
<p>We assume the fridge’s sensors and inventory-detection model already exist. This post focuses on the commerce flow after the fridge detects that something needs to be bought.</p>
<h2>Why GreenMart uses the Suite instead of building ACP itself</h2>
<p>A marketplace’s product information is usually scattered across catalog, inventory, pricing, delivery, and order-management systems. Implementing ACP directly means turning those systems into a protocol-facing product feed and checkout API, keeping that interface available, and maintaining compatibility as the protocol evolves.</p>
<p>GreenMart already needs to own the source data and business decisions: what is in stock, what substitutions are acceptable, which delivery windows are possible, and whether an order can be fulfilled. It does not need to own the agent-facing protocol surface as well.</p>
<p>With the Stripe Agentic Commerce Suite, GreenMart provides its catalog and relevant commerce information to Stripe. The Suite exposes the hosted ACP endpoint that agents call. GreenMart retains its role as the merchant and source of truth for inventory, pricing, fulfillment, and order operations.</p>
<p>The benefit is practical:</p>
<ul>
<li>GreenMart can make its catalog available to agentic surfaces without separately implementing discovery, feed hosting, and protocol routing.</li>
<li>The fridge agent can use a standard ACP interaction rather than a GreenMart-specific integration.</li>
<li>The companion mobile app can display the same authoritative checkout state that the agent received.</li>
<li>GreenMart can concentrate engineering effort on catalog quality, inventory freshness, and fulfillment—not protocol infrastructure.</li>
</ul>
<p>The hosted endpoint is an abstraction boundary, not a replacement for GreenMart’s commerce system. GreenMart must still keep the underlying information accurate and re-check price and availability before an order is accepted.</p>
<h2>The GreenMart shopping agent</h2>
<p>Every Friday, the fridge checks the household’s inventory and prepares a grocery order for Saturday-morning delivery. The household has configured preferred brands, dietary requirements, acceptable substitutions, delivery windows, and a weekly spending limit.</p>
<p>The fridge also supports an urgent mode. If milk or eggs run out, the agent can request same-day delivery—but only for approved items, within a lower budget, and without automatically accepting substitutions.</p>
<p>The roles stay deliberately separate:</p>
<ul>
<li>The fridge identifies what is missing.</li>
<li>The shopping agent builds a basket and communicates with GreenMart through ACP.</li>
<li>Stripe Agentic Commerce Suite hosts GreenMart’s ACP-facing catalog and checkout surface.</li>
<li>GreenMart provides the source information and performs its normal inventory, fulfillment, and order operations.</li>
<li>Household policy decides whether the agent may request payment.</li>
<li>The household payment authorization experience decides whether payment authority is issued.</li>
</ul>
<h2>One hosted marketplace surface, two ACP clients</h2>
<p>GreenMart sends catalog, price, inventory, and delivery information from its commerce systems to Stripe Agentic Commerce Suite. The Suite exposes that information through a hosted ACP endpoint.</p>
<p>The fridge buying agent is one ACP client. It uses the endpoint to discover products and create a checkout. The companion mobile app is the household-facing ACP client: it uses the final checkout state to show what the agent wants to buy, apply household policy, and initiate the supported payment-authorization flow.</p>
<h2><img src="https://stripe.dev/images/agentic-payments-for-marketplaces/image2.png" alt="A diagram of ACP data flow"/></h2>
<p>This is why the hosted endpoint is useful: GreenMart avoids a bespoke protocol implementation, while the agent and app still have a consistent way to exchange discovery and checkout information.</p>
<h2>The end-to-end flow</h2>
<p>Here is how the scheduled or urgent grocery order moves through the system.</p>
<p><img src="https://stripe.dev/images/agentic-payments-for-marketplaces/image1.png" alt="A diagram of the entire flow"/></p>
<p>The agent never needs to understand GreenMart’s internal catalog schema. It sees the information through ACP. GreenMart never needs to build a custom API for this agent. It supplies information to the Suite and receives the resulting order in its normal fulfillment flow.</p>
<h2>The household policy is core to the companion app</h2>
<p>Household policy is a concrete configuration owned by the people who control the bridge between the household’s agent and payment authority. The companion app should give them a way to define who may buy, what they may buy, and when the agent must stop and ask.</p>
<p>A policy setup flow can combine:</p>
<ul>
<li>Guided controls: approved agents, merchants or categories, item exclusions, per-order and weekly limits, delivery locations and time windows, substitution rules, and approval thresholds.</li>
<li>Policy templates: starting points for common arrangements, such as routine household replenishment, emergency purchases, or a child’s limited spending allowance.</li>
<li>Natural-language authoring: as an optional convenience, an LLM can turn a request such as “restock essentials up to €80 per week, but always ask before changing brands” into proposed structured rules. The household should review and confirm the resulting rules before they are enabled.</li>
<li>Lifecycle controls: named policy owners, policy versions, an audit trail, expiration dates, and a quick way to pause or revoke the agent’s authority.</li>
</ul>
<p>Evaluate the final checkout, not an estimate</p>
<p>The household policy is not a payment credential. It determines whether the agent may request payment for this specific final checkout. If the amount, item, seller, or delivery terms violate policy, the agent can choose another option or ask the household to approve an exception.</p>
<p>That evaluation uses a deterministic policy engine against the merchant, items, quantities, total, currency, selected fulfillment option, delivery address or window, substitutions, and any applicable budget already spent.</p>
<p>The result should be explicit: approved, requires household approval, or denied.</p>
<p>An approved result allows the app to request bounded payment authority through Link or another supported payment path. A checkout that exceeds a limit or introduces an unapproved substitution can be revised by the agent, escalated to a household member, or canceled. This keeps the policy understandable and auditable: an LLM may help people write it, but it should not be the component that decides whether a particular payment is allowed.</p>
<p>Below is an example of authoritative results after the shopping agent creates or updates the checkout through GreenMart’s Stripe-hosted ACP endpoint.</p>
<pre><code class="language-json">{
  &quot;id&quot;: &quot;chk_greenmart_123&quot;,
  &quot;status&quot;: &quot;ready_for_payment&quot;,
  &quot;currency&quot;: &quot;usd&quot;,
  &quot;line_items&quot;: [
    {
      &quot;id&quot;: &quot;li_milk_organic_2l&quot;,
      &quot;item&quot;: {
        &quot;id&quot;: &quot;gm_milk_organic_2l&quot;,
        &quot;name&quot;: &quot;Organic 2% milk&quot;,
        &quot;unit_amount&quot;: 449
      },
      &quot;name&quot;: &quot;Organic 2% milk&quot;,
      &quot;unit_amount&quot;: 449,
      &quot;quantity&quot;: 2,
      &quot;availability_status&quot;: &quot;in_stock&quot;,
      &quot;totals&quot;: [
        {
          &quot;type&quot;: &quot;subtotal&quot;,
          &quot;display_text&quot;: &quot;Organic 2% milk&quot;,
          &quot;amount&quot;: 898
        }
      ]
    },
    {
      &quot;id&quot;: &quot;li_eggs_free_range_12&quot;,
      &quot;item&quot;: {
        &quot;id&quot;: &quot;gm_eggs_free_range_12&quot;,
        &quot;name&quot;: &quot;Free-range eggs, 12 pack&quot;,
        &quot;unit_amount&quot;: 487
      },
      &quot;name&quot;: &quot;Free-range eggs, 12 pack&quot;,
      &quot;unit_amount&quot;: 487,
      &quot;quantity&quot;: 2,
      &quot;availability_status&quot;: &quot;in_stock&quot;,
      &quot;totals&quot;: [
        {
          &quot;type&quot;: &quot;subtotal&quot;,
          &quot;display_text&quot;: &quot;Free-range eggs, 12 pack&quot;,
          &quot;amount&quot;: 974
        }
      ]
    }
  ],
  &quot;fulfillment_options&quot;: [
    {
      &quot;id&quot;: &quot;delivery_sat_0900_1100&quot;,
      &quot;type&quot;: &quot;shipping&quot;,
      &quot;title&quot;: &quot;Saturday delivery, 09:00–11:00&quot;,
      &quot;selected&quot;: true,
      &quot;totals&quot;: [
        {
          &quot;type&quot;: &quot;shipping&quot;,
          &quot;display_text&quot;: &quot;Delivery fee&quot;,
          &quot;amount&quot;: 499
        }
      ]
    },
    {
      &quot;id&quot;: &quot;delivery_sat_1100_1300&quot;,
      &quot;type&quot;: &quot;shipping&quot;,
      &quot;title&quot;: &quot;Saturday delivery, 11:00–13:00&quot;,
      &quot;selected&quot;: false,
      &quot;totals&quot;: [
        {
          &quot;type&quot;: &quot;shipping&quot;,
          &quot;display_text&quot;: &quot;Delivery fee&quot;,
          &quot;amount&quot;: 299
        }
      ]
    }
  ],
  &quot;totals&quot;: [
    {
      &quot;type&quot;: &quot;subtotal&quot;,
      &quot;display_text&quot;: &quot;Subtotal&quot;,
      &quot;amount&quot;: 1872
    },
    {
      &quot;type&quot;: &quot;shipping&quot;,
      &quot;display_text&quot;: &quot;Delivery fee&quot;,
      &quot;amount&quot;: 499
    },
    {
      &quot;type&quot;: &quot;tax&quot;,
      &quot;display_text&quot;: &quot;Tax&quot;,
      &quot;amount&quot;: 13
    },
    {
      &quot;type&quot;: &quot;total&quot;,
      &quot;display_text&quot;: &quot;Total&quot;,
      &quot;amount&quot;: 2384
    }
  ],
  &quot;messages&quot;: []
}
</code></pre>
<p>Which becomes the following policy-evaluation view derived by the fridge:</p>
<pre><code class="language-json">{
  &quot;checkout_id&quot;: &quot;chk_greenmart_123&quot;,
  &quot;seller&quot;: &quot;GreenMart&quot;,
  &quot;total&quot;: 2384,
  &quot;currency&quot;: &quot;usd&quot;,
  &quot;selected_delivery&quot;: &quot;Saturday delivery, 09:00–11:00&quot;,
  &quot;items&quot;: [
    {
      &quot;product_id&quot;: &quot;gm_milk_organic_2l&quot;,
      &quot;quantity&quot;: 2,
      &quot;amount&quot;: 898
    },
    {
      &quot;product_id&quot;: &quot;gm_eggs_free_range_12&quot;,
      &quot;quantity&quot;: 2,
      &quot;amount&quot;: 974
    }
  ]
}
</code></pre>
<p><code>id</code>, <code>status</code>, <code>line_items</code>, <code>fulfillment_options</code>, and <code>totals</code> are ACP checkout concepts. The actual names and optional fields should follow the current <a href="https://github.com/agentic-commerce-protocol/agentic-commerce-protocol/blob/main/spec/2026-04-17/json-schema/schema.agentic_checkout.json">ACP checkout schema</a>.</p>
<h2>Where payment authority lives</h2>
<p>The app does not store card details. It uses the final ACP checkout as the purchase context, applies the household’s policy, and can either ask for an explicit approval or let the household grant the shopping agent bounded access to its Link wallet for agents.</p>
<p>That gives the full system two controls:</p>
<ol>
<li>Household policy decides whether the agent may request payment for this particular checkout.</li>
<li>The household chooses how payment authority is issued: explicit approval for a purchase, or a pre-authorized Link capability with guardrails.</li>
</ol>
<p>Link is an optional payment-authority layer, not a prerequisite for ACP or the Agentic Commerce Suite. An implementation can still use a different supported wallet or a direct SPT integration when that is the right product choice.</p>
<h2>Optional: let Link handle the payment credential</h2>
<p>If the household gives the shopping agent access to its Link wallet for agents, the agent does not need to handle card data—or, for a Stripe-processing marketplace, the details of an SPT—at all. It still needs the final checkout and household policy decision before it asks Link to pay.</p>
<h3>GreenMart processes payments on Stripe</h3>
<p>For a marketplace like GreenMart that processes payments on Stripe, Link can provide the agent’s payment capability and handle the Stripe-specific credential handoff. Under the hood, the Stripe payment flow uses a Shared Payment Token and a <code>PaymentIntent</code> to process the transaction. GreenMart receives the payment result, not the household’s card details.</p>
<p>That means the buyer-facing flow can stay simple:</p>
<ol>
<li>The agent creates an ACP checkout through GreenMart’s Stripe-hosted endpoint.</li>
<li>The companion app evaluates the final checkout against household policy.</li>
<li>Link authorizes the agent to pay GreenMart within the household’s limits.</li>
<li>The hosted checkout completes and GreenMart receives the confirmed order.</li>
</ol>
<p>The agent and companion app do not need to create, transport, or interpret an spt_… identifier in this path. SPTs remain the Stripe plumbing behind the authorization. For the lower-level model, a merchant creates a <code>PaymentIntent</code> using the granted SPT; see <a href="https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens">Shared Payment Tokens</a>.</p>
<h3>GreenMart does not process payments on Stripe</h3>
<p>Link can also support an agent purchasing from a merchant that is not processing payments on Stripe by issuing a virtual card for the transaction. The virtual card can carry the same kind of bounded authority the household intended—for example, a capped amount and a short expiry window of a few hours—rather than exposing the household’s reusable card credentials.</p>
<p>This gives the agent a payment credential that a non-Stripe merchant can accept through its existing card-processing stack. Availability and the exact guardrails depend on the Link and Issuing-for-agents capabilities enabled for the household and merchant, so production teams should validate the current product and integration requirements before relying on this path.</p>
<h2>Use direct SPTs only when you need the lower-level integration</h2>
<p>A direct SPT flow remains useful when the agent, wallet, or marketplace needs to control the token exchange explicitly. In that model, after policy approval a supported wallet returns a GreenMart-specific SPT; the marketplace then uses it to create a <code>PaymentIntent</code> on Stripe.</p>
<p>Whether Link abstracts the token or the integration passes an SPT directly, the security boundary is the same:</p>
<ul>
<li>The agent receives bounded payment authority, not a card number.</li>
<li>GreenMart receives a confirmed order through its commerce integration.</li>
<li>Retries must be idempotent, and payment/order events must be reconciled through verified webhooks.</li>
<li>Revoked or expired payment authority must prevent order completion.</li>
</ul>
<h2>Design for the moments when the agent should stop</h2>
<p>Suppose GreenMart’s preferred milk is unavailable. The marketplace offers a larger replacement, but the replacement raises an urgent order from $23.84 to $26.10.</p>
<p>The agent should not silently accept the change if it exceeds the household’s policy. It can find another eligible product, remove the unavailable item, select a lower-cost delivery window, or ask the household to approve the higher total.</p>
<p>That is what bounded autonomy looks like: the agent can take routine action quickly, and it can recognize when the decision belongs to a person.</p>
<h2>Where the Model Context Protocol fits</h2>
<p>This architecture does not require the Model Context Protocol (<a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a>)  on the purchase path. Stripe Agentic Commerce Suite gives GreenMart a hosted ACP endpoint, and the fridge agent and companion app use ACP to work with the marketplace.</p>
<p>MCP can still be useful for optional capabilities in MCP-enabled hosts: meal planning, loyalty lookup, recipe-based baskets, delivery recommendations, customer support, or fridge-side tools that interpret sensor data. Those extensions remain separate from GreenMart’s hosted commerce surface.</p>
<h2>Reference implementation</h2>
<p>The companion sample implements mocked fridge inventory signals, household policy evaluation, both an ACP- and ACS- based checkout, a household payment-authorization step, SPT-based payment, and webhook-safe order confirmation. Its developer console shows the full flow, including the distinction between a policy decision and payment authorization.</p>
<p><a href="https://github.com/aandresdelvalle-stripe/smart-fridge-agentic-commerce">Explore the Smart Fridge Agentic Commerce reference implementation</a> on GitHub.</p>]]></content:encoded></item><item><title><![CDATA[Meet Stripe's Knowledge AI Platform]]></title><description><![CDATA[Stripe's Knowledge AI Platform is our versatile AI agent platform built to handle diverse non-coding knowledge work, from quick queries to complex, multi-day projects. By connecting employees to over 1,000 internal tools and skills, it enables secure, enterprise-scale productivity across the organization.]]></description><link>https://stripe.dev/blog/meet-stripes-knowledge-ai-platform</link><guid isPermaLink="true">https://stripe.dev/blog/meet-stripes-knowledge-ai-platform</guid><dc:creator><![CDATA[Anna Mason]]></dc:creator><pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image5.gif"/><link rel="preload" as="image" href="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image3.png"/><link rel="preload" as="image" href="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image4.png"/><link rel="preload" as="image" href="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image1.png"/><link rel="preload" as="image" href="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image2.png"/><p>Coding agents transformed engineering at Stripe, but non-engineers like sales reps, finance analysts, technical account managers, and others felt left behind by the AI wave of Claude Code and Codex. No existing tool could handle the data security requirements and specific workflows Stripe needed: querying data warehouses, researching accounts before sales calls, triaging incidents, modeling revenue scenarios, or preparing compliance reviews. That all changed when we shipped Stripe’s knowledge AI platform.</p>
<p><img src="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image5.gif" alt=""/></p>
<p>Within two weeks of our April launch, most of Stripe was using Stripe&#x27;s Knowledge AI Platform, also known as Kai. Today, 83% are weekly active users, including nearly all of GTM (marketing, sales, customer success managers, and technical account managers). Most Kai sessions require many turns, with users doing deep research, creating specific artifacts, or refining assets before sharing internally or externally.  With Kai, everyone at Stripe has an agent built specifically to help them with their day-to-day work.</p>
<h2>Why we built a Knowledge AI Platform</h2>
<p>For coding tasks, the specific change varies, but the workflow and tools needed to perform the task stay about the same. You edit files, run tests, commit. The programming languages vary, but the shape of the work is rather uniform, which is why a single agent architecture works well. Knowledge work is the opposite side of that spectrum. Tasks like researching an account or preparing a compliance review require different tools, different data, different outputs, and different definitions of &quot;done&quot;.</p>
<p>Before Kai, we had two AI options for knowledge work:</p>
<ul>
<li><strong>NoCode Agent Builder</strong>: Anyone could build and deploy workflow-specific agents that could use tools. Over 4,000 agents were built using this system. However, we quickly noticed that teams were writing conceptually similar prompts but with varying levels of quality, and found the proliferation of these micro-agents increasingly hard to monitor and maintain.</li>
<li><strong>Coding agents</strong>: Coding agents were powerful, but they introduced a different set of risks. Since our goal is to enable improved productivity for all of Stripe, some users altered their workflows and chose coding agents.  However, security concerns quickly emerged, along with a new support burden for code quality teams that had never supported non-engineers before.</li>
</ul>
<p>From these experiences, we realized that building a knowledge AI platform required getting three things right: scaling expertise without centralizing it, meeting users wherever they work, and enforcing guardrails that don&#x27;t exist in code.</p>
<h3>Scaling expertise without centralizing it</h3>
<p>The range of expertise required to serve Stripe’s users is staggering. The people who know how to triage a billing escalation or model a revenue scenario are not centralized and certainly aren&#x27;t on the team building the agent infrastructure. They&#x27;re distributed across dozens of specialized domains – GTM, Finance, Marketing, Legal, Data Science, and more – each with its own tools, data sources, workflows, and judgment about what &quot;good&quot; looks like. Multiply this across all the products and countries Stripe operates in, and the result is astonishingly complex. Kai has to model that complexity in a way that makes it invisible, so that the task just works.</p>
<h3>The agent has to roam</h3>
<p>Where the agent surfaces matters as much as what it knows. Not everyone works in a browser tab, and fewer still in a terminal. So a knowledge agent can&#x27;t just be a single product; it has to be a platform, flexible enough to embed itself wherever work happens.</p>
<p>For example, consider an internal application our Finance team uses to model complex changes in Stripe&#x27;s operating budget: the agent needs to read the context, research related documents, propose valid changes, and summarize the differences, all without pulling them out of the app.</p>
<p>Building a standalone agent product wouldn&#x27;t work.  Instead, it would force users out of their natural workflows and into a new app. Building a separate agent product for each surface wouldn’t work either; it’d be hard to maintain, and users who span multiple tools would have a fractured experience. The platform has to meet the user where they are.</p>
<h3>Building guardrails from scratch</h3>
<p>Coding agents operate in an environment with decades of fast, verifiable guardrails: compilers reject invalid syntax, tests catch regressions, and git makes every mistake reversible. Knowledge work has very little support for these constructs.</p>
<p>Consider a core invariant at Stripe: <em>“you shouldn&#x27;t combine data from two unrelated customer contexts in a single analysis.”</em> A user may have legitimate access to both contexts independently, but they can never appear in the same session. The isolation boundary isn&#x27;t <em>&quot;what can this person access based on their authorization token?&quot;</em>, but instead <em>&quot;what should this task be allowed to view given this context?&quot;</em>  The platform has to enforce these implicit guardrails that users rely upon.</p>
<h2>How we built Kai</h2>
<p>A single monolithic agent simply cannot encode all of these constraints effectively. And, asking every domain team to independently build secure, hosted, performant agent infrastructure doesn&#x27;t scale either. To manage this challenge, we built Kai in three layers:</p>
<ul>
<li><strong>Surface-agnostic APIs</strong> that give multiple interfaces into the same agent</li>
<li><strong>Agent Studio</strong> where domain owners build and govern their own Kai agents, and</li>
<li><strong>Execution environments</strong> to deliver security in seconds without anyone thinking about infrastructure.</li>
</ul>
<h3>Surface-agnostic APIs</h3>
<p>Kai ships with an opinionated web application and a Slack integration, but the main primitive is the underlying API that powers them both. The agent is a service, not an application, and surfaces are simply customized views into it.</p>
<p>Most Stripes interact with Kai through the internally hosted web application. There&#x27;s no infrastructure to set up – it&#x27;s available to every employee on Day 0.</p>
<p>Any internal tool can also embed Kai, and many have chosen to do so. For example an employee working in our business intelligence platform can ask Kai a question from within their existing application because our Chrome extensions surface Kai capabilities inside web-based third-party tools.</p>
<p><img src="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image3.png" alt=""/></p>
<blockquote>
<p>Custom applications embed Kai via APIs to bring agentic experiences to all workflows</p>
</blockquote>
<h3>Agent Studio</h3>
<p>Agent Studio is the control plane for domain owners. Teams use it to build, test, and monitor their skills, custom Kai agents, and tool selections. A GTM team, for example, owns a Kai agent tuned to their workflows. It loads their skills by default, connects to their data sources, and presents outputs in the format their users expect. Agent Studio surfaces usage data and quality signals alongside each asset, so domain owners can see what&#x27;s working without asking the platform team.</p>
<p><img src="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image4.png" alt=""/></p>
<blockquote>
<p>Assets are organized into areas across Stripe that are managed by domain experts</p>
</blockquote>
<h3>The execution environment</h3>
<p><img src="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image1.png" alt=""/></p>
<p>This is the layer that makes the platform&#x27;s promises real. The core primitives, including the agent harness, sandbox, workflow orchestration, and access control framework, are deliberately shared with Stripe&#x27;s product-facing agents. Internal knowledge work operates on the same sensitive data and serves the same users as our external products, so it requires the same security and compliance bar. Sharing the substrate forces discipline and creates a flywheel: improvements to the execution environment benefit both internal and product agents simultaneously.</p>
<p>The agent harness, built using <a href="https://github.com/langchain-ai/deepagents">LangChain’s deepagents</a>, runs on Kubernetes with a secure per-session sandbox and a multi-tenant virtual filesystem. Within a session, the agent works with a virtual filesystem where it creates and iterates on artifacts, while a secure code execution sandbox is used for analytics and data processing.</p>
<p>It&#x27;s built to hold state across long, complex sessions — one recently reached 932 turns. With Kai’s deep task management capabilities, a single conversation can consist of hundreds of turns, and hundreds of tool and LLM calls without timing out or overloading the context window. This matters because knowledge work is rarely a single question. It&#x27;s iterative reasoning that builds on itself, and the session has to hold that state without degrading.</p>
<p><img src="https://stripe.dev/images/meet-stripes-knowledge-ai-platform/image2.png" alt=""/></p>
<blockquote>
<p>User behaviors are changing, and sessions are increasingly used for deep multi-turn collaboration</p>
</blockquote>
<p>One of the most interesting parts of the harness is how it chooses the correct skill to use. Kai is connected to 1,000+ skills and tools spanning various internal systems – from business intelligence dashboards that track key metrics, to project management tools that organize internal execution, and third-party services like Zoom and Google Workspace. Anyone can ask it a question and trust it will load the right context and use the right tools to get the job done. Coding agents have a natural advantage here: the folders they work provide a natural organization for skills and context. In a follow-up post, we’ll go into how we solved this without that pre-existing structure by utilizing a hybrid RAG/LLM approach, among other techniques.</p>
<h2>Impact</h2>
<p>The results have been striking. New hires on GTM are Kai-native: they use it 2.7x more, and power users close 80% more value than low users within the same cohort. When Account Executives use Kai, they produce 2x the sales activity, create 17% more opportunities, generate 26% more revenue opportunities, and close 39% more deals when compared to the same sellers in weeks they don&#x27;t use it. In aggregate, Kai has helped shift 25,000 hours per year from administrative work, to revenue generating work.</p>
<p>In finance and operations, Kai is helping Stripes analyze messy data, generate recurring digests, and turn fragmented context into usable artifacts.</p>
<p>In engineering, Kai is now a natural place to ask system questions, research for run requests, analyze logs, draft plans, and invoke more specialized agents and skills.</p>
<p>And across Stripe, more than 5,000 sessions everyday center on data analysis. This makes Kai a unique leverage point: by plugging in the right context about data quality and our analytics layer, we can ensure correct responses by default for most questions.</p>
<p>Stripes’ direct feedback backs up these aggregates: they report feeling “empowered to embrace AI” and “astounded at what Kai just does precisely correct”. But our favorite anecdote is a non-engineer who left a Kai intro session and immediately collaborated on a digest that pulls together Asana, Slack, and Jira into a single automated process.</p>
<h2>We haven’t won yet</h2>
<p>At Stripe, one of our favorite catchphrases is “we haven’t won yet”, and that’s apt for Kai. We’re in the earliest stages of this journey, and have a lot more we want to do:</p>
<ul>
<li><strong>Better state management:</strong> General-purpose agents like Kai generate a lot of state as they iteratively make tool calls, pull down large documents, and more. We are continuously tuning both the “active” context being sent to the LLM and the “extended” context sitting in state stores like S3 or the virtual file system.</li>
<li><strong>Reflection and self-improvement:</strong> We are working on a quality improvement loop that lets Kai reflect on traces involving a skill, propose improvements, test them, and submit changes for the skill owner to review.</li>
<li><strong>Better collaboration primitives:</strong> Users generate a lot of context in their Kai sessions that is currently “locked in”, but that’s not how work gets done. We want to let people share what Kai surfaces across sessions, and let multiple people (and agents!) collaborate on the same artifacts.</li>
</ul>
<p>Coding agents made AI feel concrete for software engineers first. Kai has helped bring that same sense of leverage to knowledge workers across Stripe. It’s interesting because while we have improved productivity considerably, we don&#x27;t know the ceiling yet - and we’re excited to keep pushing the envelope here. If building agentic systems that power internal productivity and external agents sounds like your kind of challenge, <a href="https://grnh.se/b5wzjpge1us">we’re hiring</a>.</p>]]></content:encoded></item><item><title><![CDATA[How Stripe uses graph search and state machines to auto-remediate a global database fleet]]></title><description><![CDATA[Discover how we modeled our MongoDB infrastructure as a traversable graph and then use pathfinding algorithms to dynamically compute and execute recovery plans. This automated approach reduced pager volume by 30% (~200 pages/year), eliminated 12 days of unhealthy shard states annually, and supports new shard layouts with zero manual effort.]]></description><link>https://stripe.dev/blog/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet</link><guid isPermaLink="true">https://stripe.dev/blog/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet</guid><dc:creator><![CDATA[Pragya Mehta]]></dc:creator><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet/image1.gif"/><link rel="preload" as="image" href="https://stripe.dev/images/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet/image2.png"/><link rel="preload" as="image" href="https://stripe.dev/images/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet/image3.png"/><p>At 2 a.m. on a Tuesday, an on-call engineer&#x27;s pager fires. A MongoDB shard—one of 2,000 that collectively processed $1.9 trillion in payments in 2025—has entered a degraded state. Two nodes are down, and a third is blocking an index build due to a misconfigured vote. To nurse the shard back to health, the engineer will need to spend 15 minutes carefully sequencing manual operations, then up to 3 hours triggering and monitoring each operation.</p>
<p>This scenario played out hundreds of times a year across Stripe&#x27;s Document Database fleet—our global MongoDB infrastructure spanning over 40 distinct shard layouts, from cost-optimized configurations to high-availability setups designed for disaster recovery. The combinatorial explosion of failure modes made it impossible to anticipate every scenario with static, hand-coded automation. We needed a fundamentally different approach.</p>
<p>Our solution treats infrastructure state as a traversable graph and lets a pathfinding algorithm discover recovery sequences at runtime. By applying breadth-first search, and later, Dijkstra&#x27;s algorithm, over a simulated state machine, we built a system that dynamically computes and executes multi-step recovery plans. As a result, we’ve seen a 30% reduction in pager volume (~200 fewer pages per year), 12 fewer days of shards stuck in unhealthy states annually, and zero additional work required to support new shard layouts.</p>
<h2>Why static automation breaks at scale</h2>
<p>Our original auto-remediation system (V1) operated as a single-step, hard-coded state machine. A set of plugins—each responsible for one issue, like &quot;fix votes,&quot; &quot;fix priority,&quot; or &quot;rebuild downed node&quot;—ran in a carefully ordered sequence based on hard-coded ranking and assumptions within a reconciliation loop.</p>
<p>This worked well for simple cases, like if a single node went down, or one or two misconfigured votes, but it had fundamental limitations:</p>
<ul>
<li>
<p>Implicit dependencies between plugins meant that ordering was fragile. The &quot;fix votes&quot; and &quot;fix priorities&quot; plugins required no nodes to be down, but they had higher priority than the &quot;node down&quot; plugin. We could only resolve these circular dependencies with manual intervention.</p>
</li>
<li>
<p>Multi-failure scenarios exposed blind spots. When multiple impaired nodes combined with voting and priority misconfigurations, the sequential, single-issue-at-a-time approach couldn&#x27;t find a valid ordering. The system would exhaust its options and page an operator.</p>
</li>
<li>
<p>Layout coupling was everywhere. Each plugin contained layout-specific safety checks, so onboarding a new layout to the V1 remediation platform required auditing the logic of every plugin and workflow, a process that took about 1 week for each new layout.</p>
</li>
<li>
<p>Workflow cancellations left shards stranded. A canceled workflow could leave the shard in an intermediate state that V1 never anticipated. These orphaned states fell outside any plugin&#x27;s preconditions and required manual cleanup.</p>
</li>
</ul>
<p>In one six-month window, the control plane paged operators 124 times for misconfigured shards, and 32 times for single-node-down scenarios complicated by additional health issues. Critical operations like index builds and planned maintenance were blocked for an average of one hour per incident.</p>
<h2>Infrastructure as a traversable graph</h2>
<h3>Reframing the question</h3>
<p>The breakthrough came when we stopped asking, &quot;What&#x27;s the most important issue to fix right now?&quot;, which is a local, greedy question. Instead, we asked, &quot;What sequence of operations transforms the current shard state into a healthy state without ever violating safety invariants?&quot; Once we framed it that way, we realized we were looking at a graph search problem.</p>
<p><img src="https://stripe.dev/images/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet/image1.gif" alt=""/></p>
<h3>Modeling a shard’s health as a state machine</h3>
<p>In MongoDB, cluster topology is controlled by assigning nodes specific roles: voting rights determine participation in leader elections and quorum writes, priority levels control a node&#x27;s eligibility to become the primary, and hidden status keeps a node out of the client read path. We capture these per-node attributes, along with shard-level properties like oplog sizes, regional distribution, and quorum status, to model a shard’s state as a node in a directed graph.</p>
<p>Edges in the graph represent atomic infrastructure operations: adding or removing a vote or priority, hiding or unhiding a node, rebuilding a node, or resizing an oplog. These are steps we need to take on the path to getting the shard healthy again.</p>
<h3>Finding the path with breadth-first search over simulated states</h3>
<p>The initial state is the shard&#x27;s current ground reality. The goal state, the desired, healthy configuration, is the shard&#x27;s Source of Truth (SOT). Invalid states violate safety invariants. These could be an even number of votes, a minority of healthy voting nodes, which puts write availability at risk, or a node in an invalid state, like priority=1 and vote=0.</p>
<p>With the state space defined, we use breadth-first search (BFS) to find the shortest sequence of operations from the current state to the goal state. The algorithm generates successor states at each step, prunes any that are invalid, and returns the first path that reaches the goal. Since BFS traverses level-by-level, it guarantees that the first solution uses the minimum number of operations.</p>
<p>Consider a healthy shard where three things go wrong simultaneously: a data node goes down, the backup node goes down, and another data node loses its vote and priority. V1 is completely stuck—the vote-fix plugin requires no nodes to be down, and the node-rebuild plugin requires votes to be correct. Our state machine, however, explores all valid orderings:</p>
<pre><code>Current State → [Rebuild backup] → [Fix vote+priority] → [Rebuild data node] → Healthy
Current State → [Fix vote+priority] → [Rebuild backup] → [Rebuild data node] → Healthy
Current State → [Fix vote+priority] → [Rebuild data node] → [Rebuild backup] → Healthy
</code></pre>
<p>Each path is validated against invariants at every intermediate step. Fixing the vote/priority issue first is safe since it maintains quorum, which then unlocks subsequent operations.</p>
<h3>In-memory simulation and durable execution with Temporal</h3>
<p>We orchestrate our remediation workflows with <a href="https://temporal.io/">Temporal</a>, a durable execution framework that guarantees a workflow interrupted by a deploy, crash, or timeout can resume exactly where it left off—essential for node rebuilds that can take hours.</p>
<p>But Temporal&#x27;s durability model creates a challenge for pathfinding. Our remediation building blocks are implemented as Temporal workflows and activities that are tightly coupled to Temporal&#x27;s workflow.Context. They make network calls, wait for infrastructure changes, and checkpoint progress. We can&#x27;t call these functions in a loop to simulate thousands of state transitions. A single simulated path through a five-step recovery would take minutes of real I/O; exploring hundreds of candidate paths would be intractable.</p>
<p>We solved this by introducing an abstraction layer: a CommonContext interface that can be backed by either Temporal&#x27;s real workflow context, or a lightweight in-memory implementation.</p>
<pre><code class="language-go">// CommonContext abstracts Temporal&#x27;s workflow.Context, enabling both
// production execution and local simulation.
type CommonContext interface {
        ExecuteActivity(opts workflow.ActivityOptions, resultPtr any, activity any, args ...any) error

        ExecuteLocalActivity(opts workflow.LocalActivityOptions, resultPtr any, activity any, args ...any) error

        ExecuteWorkflow(opts workflow.ChildWorkflowOptions, resultPtr any, workflow any, args ...any) error

        WithExternalDeps(sotProvider, grProvider SerializedShardProviderData, simState *SMShardState) (CommonContext, error)
}
</code></pre>
<p>The interface wraps Temporal&#x27;s core primitives so that remediation logic can call them without knowing whether it&#x27;s running inside a real workflow or a local simulation. The key method is WithExternalDeps, which swaps in simulated SOT and Ground Reality providers along with an in-memory shard state, allowing the system to explore thousands of state transitions without touching real infrastructure. In production, CommonContext delegates to Temporal and makes real API calls. In simulation, it delegates to an in-memory state provider that applies operations as pure state transformations. Both modes run the same business logic.</p>
<p>This gives us a critical property: what you simulate is what you execute. There’s no separate planning codebase that could diverge from the execution codebase. When the simulator determines that a sequence of operations will heal a shard, we have very high confidence that executing those same operations in production will produce the same result.</p>
<p>The simulation approach transforms minutes of I/O-bound execution into just milliseconds of CPU-bound computation. The BFS pathfinder can explore hundreds of state transitions per second, evaluating complete multi-step recovery paths in the time it would take to make a single network call.</p>
<h2>Evolving the algorithm: From BFS to Dijkstra</h2>
<p>BFS finds the optimal path to a fully healthy state, but it fails entirely if no complete path exists. Consider a shard with an extra node that automation can’t reach, alongside another node that has a missing vote. BFS returns no path at all, because the full goal state is unreachable—even though it’s trivial to fix the missing vote, and leaving it unfixed puts the shard at elevated risk of losing write quorum.</p>
<p>When our algorithm couldn’t reach a perfectly healthy state, could it find the least degraded state instead?</p>
<h3>Weighted graph search</h3>
<p>We evolved the algorithm to use Dijkstra&#x27;s shortest-path algorithm over a weighted graph. The edge weight for each operation accounts for misconfiguration and estimated time:</p>
<pre><code>edgeWeight(state, operation) = misconfiguration(state) × estimatedTime(operation)
</code></pre>
<p>misconfiguration(state) quantifies how far the current state deviates from healthy, using a rules-based scoring system that accounts for missing votes, incorrect priorities, downed nodes, and other issues. estimatedTime(operation) estimates wall-clock duration–a vote change takes seconds, while a node rebuild takes an hour. Using the product of these two for edge weight means the algorithm minimizes time spent in misconfigured states, prioritizing quick fixes that reduce exposure before committing to longer operations.</p>
<p><img src="https://stripe.dev/images/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet/image2.png" alt=""/></p>
<h3>Partial remediation</h3>
<p>Because Dijkstra&#x27;s algorithm explores paths to all reachable states rather than only the goal state, we can also get partial remediation. When no complete path exists, the algorithm returns the path to the least misconfigured state. The ephemeral-node example resolves itself: Dijkstra fixes the vote, reduces the shard&#x27;s risk, and leaves the ephemeral node for operator attention.</p>
<p><img src="https://stripe.dev/images/how-stripe-uses-graph-search-and-state-machines-to-auto-remediate-a-global-database-fleet/image3.png" alt=""/></p>
<h3>Extensibility through composable rules</h3>
<p>The misconfiguration scoring is designed as a set of composable rules. The core function curries the SOT and returns a closure that can rapidly score any candidate state during graph traversal:</p>
<pre><code class="language-go">type MisconfigRule func(*ar.SMShardState, *ar.SMShardState) int

func GetMisconfigFunc(misconfigRules map[string]MisconfigRule, sot *ar.SMShardState) func(current *ar.SMShardState) int {
    return func(current *ar.SMShardState) int {
        misconfig := 0

        for _, rule := range misconfigRules {
            misconfig += rule(current, sot)
        }
  
        return misconfig
    }
}
</code></pre>
<p>Each rule targets a specific class of misconfiguration—downed nodes, voting mismatches, priority mismatches, oplog sizing, hidden status, and availability zone imbalance—and we can add new rules independently as our understanding of severity evolves:</p>
<pre><code class="language-go">func ListAllMisconfigRules() map[string]MisconfigRule {
      return map[string]MisconfigRule{
          &quot;DownNodeMisconfigRule&quot;:     DownNodeMisconfigRule,
          &quot;VotingMisconfigRule&quot;:       VotingMisconfigRule,
          &quot;PriorityMisconfigRule&quot;:     PriorityMisconfigRule,
          &quot;OplogResizeMisconfigRule&quot;:  OplogResizeMisconfigRule,
          &quot;HiddenStatusMisconfigRule&quot;: HiddenStatusMisconfigRule,
          &quot;AZMisconfigRule&quot;:           AZMisconfigRule,
      }
  }
</code></pre>
<p>Operation costs are expressed as relative weights that reflect how dramatically operations differ in duration—a node rebuild takes roughly 33 times longer than a config change:</p>
<pre><code class="language-go">const (

      OPTIME_NODE_UPDATE_MONGO_CONFIG = 3
      OPTIME_SHARD_UPDATE_OPLOG       = 19
      OPTIME_NODE_REBUILD             = 101

  )
</code></pre>
<p>The named rule map also makes debugging straightforward. The logs for a generated path show exactly how each rule contributed to the misconfiguration score at every intermediate state.</p>
<h2>Impact</h2>
<p>The state machine auto-remediation system has fundamentally changed our operational baseline. We’ve seen a 30% reduction in pager volume, or ~200 fewer pages a year, along with 12 days of fleet health reclaimed annually from shards that used to sit in degraded states until someone noticed them. Critical operations like index builds and planned maintenance now proceed without getting bottlenecked by manual stabilization.</p>
<p>The system is also layout-independent: when we onboarded a new layout for a planned failover, auto-remediation worked with zero code changes. And because workflows can be canceled mid-execution, the system can compute a fresh recovery path from whatever intermediate state was left behind.</p>
<p>Beyond metrics, the system has changed how we think about failure. Instead of writing runbooks for anticipated scenarios, we define invariants and available operations, and let the algorithm compose them into recovery plans.</p>
<h2>Future directions</h2>
<p>Right now, the state machine answers one question: &quot;How do I get this shard back to its declared healthy state?&quot; But the framework is extensible.</p>
<p>The most immediate extension is layout transformation. Our database fleet has over 40 shard layouts, and migrating a shard from one topology to another currently means writing a bespoke workflow that encodes every intermediate step. Instead, we can set the goal state and let the algorithm discover a safe migration sequence the same way it discovers a recovery sequence. We can use simple config updates for fleet-wide topology changes rather than needing to write bespoke migration workflows.</p>
<p>We can also adapt the state model to handle blue-green migrations, where a shard temporarily runs two &quot;colors&quot; of nodes during a version rollout. The pathfinder must be topology-aware, understanding which color is active and what &quot;healthy&quot; means at each phase of the migration. We want to be able to overlay migration context onto the shard&#x27;s SOT, allowing the same Dijkstra-based planner to rebalance votes, rebuild failed nodes with the correct version binary, and resize oplogs. Then we can safely automate rollbacks when health signals degrade after a traffic shift.</p>
<p>Further out, we want the system to orchestrate planned maintenance alongside reactive healing. Right now, we execute one operation at a time on a single node across the entire shard, but when a node is already offline for a version upgrade, we could batch an oplog resize or index build into the same window. The system could discover these batching opportunities by modeling &quot;node offline for maintenance&quot; as a state where multiple operations become safe to execute together.</p>
<h2>Conclusion</h2>
<p>If you can model your infrastructure&#x27;s state space and define valid transitions, recovery becomes a search problem. The engineering challenge is making that search practical—building a simulation layer that’s fast enough for exhaustive exploration, ensuring simulation fidelity matches production execution, and maintaining safety invariants that prevent the algorithm from proposing dangerous intermediate states.</p>
<p>For teams managing complex distributed infrastructure, this pattern of state machine modeling, simulation-based planning, and runtime pathfinding offers a compelling alternative to accumulating ever-more-specific runbooks. Runbooks encode known recovery procedures; a state machine discovers novel ones.</p>
<p>Special thanks to Aarush Gupta, Jessica Glustien, Neeraj Joshi, Puneet Oberai, Runchen Yan, Shalin Shekhar Mangar and Srivatsan Sridharan for their contributions to this project.</p>
<p>If building systems like this interests you, <a href="https://stripe.com/jobs/search?query=infrastructure">we&#x27;re hiring</a>.</p>]]></content:encoded></item><item><title><![CDATA[Keeping funds where they belong: Implementing fund segregation in Stripe Connect]]></title><description><![CDATA[Learn how to implement fund segregation in Stripe Connect using allocated balances to ring-fence seller funds from your platform's operational balance - covering payment creation, transfers, refunds, disputes, and preparing for upcoming PSD3 regulatory requirements.]]></description><link>https://stripe.dev/blog/fund-segregation-stripe-connect</link><guid isPermaLink="true">https://stripe.dev/blog/fund-segregation-stripe-connect</guid><dc:creator><![CDATA[Andrew Robinson]]></dc:creator><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/fund-segregation-stripe-connect/diagram.png"/><p>Many marketplaces or platforms operate on <a href="https://stripe.com/connect">Stripe Connect</a>, using the <a href="https://docs.stripe.com/connect/separate-charges-and-transfers">separate charges and transfers</a> charge type. This allows marketplaces to offer different purchase combinations for their customers, such as items from different marketplace sellers, in a single transaction, and then perform separate transfers.</p>
<p>If you operate in this model, when your platform collects a payment, those funds land into the Stripe balance on your platform account, pooled together with your own revenue. Stripe handles the accounting and reporting, but the funds themselves aren’t ring-fenced until you explicitly transfer them to a seller&#x27;s connected account.</p>
<p>PSD2 is the 2nd revision of the Payment Services Directive, a European Union law that regulates electronic payments. The latest version of this, PSD3, is due to take effect from late 2027. Under PSD2, most marketplaces and platforms operate in this way using the Commercial Agent Exemption (CAE), allowing you to process payments without a formal payment institution license. Due to upcoming changes to PSD3 and the likely tightening of Commercial Agent Exemptions, Stripe has created a new charge type for Stripe Connect called &quot;<a href="https://docs.stripe.com/connect/funds-segregation">Segregated Separate Charges and Transfers</a>&quot;.</p>
<h3>The problem with pooled balances</h3>
<p>If we take a marketplace that is processing payments for thousands of different sellers, without fund segregation, a single platform balance on Stripe holds funds for many different connected accounts (sellers), meaning that:</p>
<ul>
<li><strong>Automatic payouts</strong> scheduled on your platform account could inadvertently consume funds meant for a seller.</li>
<li><strong>Unrelated chargebacks</strong> or disputes on one transaction can draw from the shared pool, affecting funds earmarked for a completely different seller.</li>
<li><strong>Stripe fees</strong> are debited from your platform balance - the same pool that holds seller money.</li>
<li><strong>Operational errors</strong> such as an accidental refund, or misconfigured payout schedule, can leave sellers short with no clear audit trail.</li>
</ul>
<p>In isolation, each scenario may seem unlikely, but at scale they can compound into systemic risk.</p>
<h3>Why this matters now</h3>
<p>Under PSD2 and EMD2 regulations in the EU, regulators are increasingly requiring platforms and marketplaces to demonstrate that customers’ funds are safeguarded, that they are held separately from operational funds and protected from the platform’s own creditors. Within the UK, the FCA enforces similar client money rules.</p>
<p>Even in geographies outside of this regulation, fund segregation is often treated as a trust signal. Sellers want to know their money is protected, investors or auditors want to see clear separation, and engineering teams want deterministic fund flows, rather than a shared pool of funds where edge cases can compound.</p>
<h3>How fund segregation works</h3>
<p>Fund segregation in Stripe Connect introduces a new holding state for funds. Funds from payments are allocated; they exist on your platform account, but don’t appear in your available balance, can’t be used for payouts, and can only be transferred to a connected account. The diagram below shows a high level overview of the fund flow:</p>
<p><img src="https://stripe.dev/images/fund-segregation-stripe-connect/diagram.png" alt="High level overview of the fund flow"/></p>
<p>The full end-to-end flow can be described as follows:</p>
<h4>Create a payment with allocated funds</h4>
<p>When creating a <a href="https://docs.stripe.com/payments/payment-intents">PaymentIntent</a>, set <code>allocated_funds</code> to <code>enabled: true</code>. At capture time, Stripe automatically places the funds into the allocated balance within your Stripe Platform account.</p>
<p>You should also specify the <code>transfer_group</code> with your value; this value lets you track the flow of funds from payment through to transfer, and helps with reconciliation and audit flows.</p>
<pre><code class="language-javascript">{
  &quot;amount&quot;: 10000,
  &quot;currency&quot;: &quot;eur&quot;,
  &quot;allocated_funds&quot;: {
    &quot;enabled&quot;: true
  },
  &quot;transfer_group&quot;: &quot;ORDER-1234&quot;
}
</code></pre>
<p>After successful capture of funds, the full amount becomes available in your allocated balance, subject to normal payment method settlement timing.</p>
<h4>Check the allocated balance</h4>
<p>You can inspect the allocated funds balance for a specific payment intent by expanding the <code>latest_charge</code> property within the PaymentIntent object. This gives you per-payment visibility, so you know exactly how much is in your allocated balance, and how much has been transferred into a connected account.</p>
<p>In this example, we can see that <code>allocated_funds</code> is enabled, so we know the charge amount is using your allocated balance, and €98.00 is available to be transferred (€100 minus €2 example Stripe fees).</p>
<pre><code class="language-javascript">{
  &quot;id&quot;: &quot;pi_xxx&quot;,
  &quot;latest_charge&quot;: {
    &quot;id&quot;: &quot;ch_xxx&quot;,
    &quot;allocated_funds&quot;: {
      &quot;enabled&quot;: true,
      &quot;balance&quot;: {
        &quot;available&quot;: 9800,
        &quot;pending&quot;: 0,
        &quot;currency&quot;: &quot;eur&quot;
      }
    }
  }
}
</code></pre>
<h4>Transfer to connected accounts and collect your platform fee</h4>
<p>When creating the transfer object to transfer allocated funds to a connected account, set the <code>source_transaction</code> parameter to the charge ID from the original payment intent. This tells Stripe to draw from that specific payment’s allocated balance. You can also collect your platform fee in the same call by specifying the <code>application_fee_amount</code> parameter. This debits the fee from the allocated funds and credits it to your platform’s payment balance.</p>
<p>It’s important to note that the <code>application_fee_amount</code> is additive to the transfer amount. For example, if you want to collect a €5.00 application fee for a €100.00 charge, and then transfer the remaining €93.00 to a connected account, you’d need to specify this as follows:</p>
<pre><code class="language-javascript">{
  &quot;amount&quot;: 9300,
  &quot;currency&quot;: &quot;eur&quot;,
  &quot;destination&quot;: &quot;acct_seller_123&quot;,
  &quot;source_transaction&quot;: &quot;ch_xxx&quot;,
  &quot;transfer_group&quot;: &quot;ORDER-1234&quot;,
  &quot;application_fee_amount&quot;: 500
}
</code></pre>
<p>You don’t need to transfer everything at once, you can split allocated funds across multiple transfers to connected accounts; this is useful if you have mixed first-party and third-party purchases, or purchases from multiple sellers in a single transaction. One rule to remember is that the total transferred cannot exceed the original charge amount.</p>
<p>As the <code>application_fee_amount</code> is taken directly from the allocated funds on your platform account, it avoids unnecessary FX conversions when the platform and connected account operate in different currencies.</p>
<h4>Handling the hard parts</h4>
<p>In the real world, we know our users don’t just process happy path payments. Refunds, disputes, and reversals are also common, so it’s key to understand how these work using allocated balances.</p>
<h4>Refunds</h4>
<p>When you process a refund, Stripe will first draw from the remaining allocated balance before touching the balance of your platform account. As an example, if you had a €100.00 charge in your allocated balance, you transferred €60.00 to a connected account (leaving €40.00 in the allocated balance), and then did a €100.00 refund:</p>
<ol>
<li>€40.00 would come from the remaining allocated funds</li>
<li>€60.00 would come from your platform’s account balance</li>
</ol>
<p>This predictable ordering means allocated funds are always used first, reducing the frequency of debits from your platform’s account balance.</p>
<h4>Transfer reversals</h4>
<p>If you need to claw back funds from a connected account balance, for example before processing a refund or while awaiting a dispute outcome, you can use a transfer reversal. Reversed funds will return to the allocated balance, not your platform’s account balance.<br/>
When creating a reversal, if you set the <code>refund_application_fee</code> to <code>true</code>, the application fee is also returned to the allocated balance. This is an important decision for your business; some marketplaces choose to refund the application fee, while others choose to retain it.</p>
<h4>Disputes</h4>
<p>When a dispute is raised on an allocated payment, you can reverse the transfer to pull funds back from the connected account into your allocated balance whilst awaiting the outcome.</p>
<p>If the dispute is won, you can then transfer the allocated funds back to the connected account. If the dispute is lost, you can use a Balance Transfer to move these funds into your platform balance, providing the currency matches the original charge settlement currency.</p>
<p>Dispute fees are always debited from your platform balance, they never affect the allocated funds balance.</p>
<h4>Hanging balances</h4>
<p>You may find that you end up with allocated funds that no longer need to be in your allocated balance, for example after a dispute is refunded from your platform balance and you later reverse the transfer from the connected account.</p>
<p>In this scenario, you can use a Balance Transfer to move these funds from the allocated balance to your platform balance, providing the currency matches the original charge settlement currency.</p>
<h3>Getting ready</h3>
<p>Before starting, there are some pre-requisite steps worth being aware of:</p>
<ol>
<li>Your platform account must be responsible for negative balances on connected accounts</li>
<li>Include the <code>allocated_funds_preview=v1</code> parameter as part of the header on all API requests. For example, with Node you include this as a parameter when defining the Stripe const variable:</li>
</ol>
<pre><code class="language-javascript">const stripe = require(&#x27;stripe&#x27;)(
  &#x27;sk_test_&#x27;,
    {
      &#x27;allocated_funds_preview=v1&#x27;,
    }
  );
</code></pre>
<ol start="3">
<li>Allocated funds currently only work with card payment methods, so you should avoid using <code>automatic_payment_methods</code> and specify payment methods explicitly</li>
<li>Allocated balances cannot be combined with overcapture, multi capture, or incremental authorisations</li>
<li>For testing, allocated balances can only be used with sandbox accounts, they do not work in legacy test mode</li>
<li>The Stripe dashboard doesn’t distinguish between allocated balances and other account balances, you must use the API for visibility</li>
</ol>
<h3>Benefits</h3>
<p>With segregated separate charges and transfers and allocated balances, your platform can achieve:</p>
<ul>
<li><strong>Deterministic fund flows</strong> — every payment&#x27;s funds are traceable from capture to transfer, with no possibility of leakage into unrelated operations.</li>
<li><strong>Regulatory alignment</strong> — demonstrable safeguarding of customer funds, separated from platform operational balances.</li>
<li><strong>Operational safety</strong> — auto-payouts, unrelated chargebacks, and fee debits can&#x27;t touch allocated funds.</li>
<li><strong>Clear audit trail</strong> — per-payment balance visibility via the API at every stage of the lifecycle.</li>
</ul>
<p>For platforms or marketplaces that operate in regulated markets, or those handling significant seller volumes, this allows you to move to a world in which fund safety becomes enforced by the infrastructure, rather than being purely on a trust basis.</p>
<h3>Next steps</h3>
<ul>
<li>Review the full <a href="https://docs.stripe.com/connect/funds-segregation">fund segregation documentation</a></li>
<li>Set up a <a href="https://docs.stripe.com/sandboxes">Sandbox environment</a> to test your integration</li>
<li>If you&#x27;re evaluating whether fund segregation fits your regulatory requirements, reach out to your Stripe account team for guidance on your specific jurisdiction</li>
</ul>]]></content:encoded></item><item><title><![CDATA[Introducing a better way to process Stripe's thin events]]></title><description><![CDATA[Event destinations are central to Stripe integrations but hard to get right. Event notification handlers encapsulate the setup and wiring, letting you focus on business logic and surface errors at development time instead of in production.]]></description><link>https://stripe.dev/blog/event-notification-handlers-thin-events</link><guid isPermaLink="true">https://stripe.dev/blog/event-notification-handlers-thin-events</guid><dc:creator><![CDATA[David Brownman]]></dc:creator><pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Handling events is a cornerstone of a successful Stripe integration. But, correctly setting up and maintaining an <a href="https://docs.stripe.com/event-destinations">event destination</a> is an error-prone process. While authoring, there are a lot of event shapes and keys to manage; static typing doesn&#x27;t give you much guidance in our non-TypeScript SDKs.</p>
<p>Once your event destination is set up, upgrades can be tricky. Because the shape of events are tied to a specific API version, upgrading either your event destination or SDK (especially in strongly typed languages) can lead to unexpected runtime errors.</p>
<p>Stripe&#x27;s solution to the maintenance problem is <a href="https://docs.stripe.com/event-destinations#thin-events">thin events</a>, which are unversioned JSON payloads. Because they&#x27;re identical across API versions, you can use them with <em>any</em> SDK version, making upgrades much safer. But the authoring problem remains: how can the SDKs present as much information about the shape of events as early as possible?</p>
<p>Enter <a href="https://docs.stripe.com/webhooks/event-notification-handlers">event notification handlers</a>. They encapsulate everything your event handler needs to run and let you focus on your business logic. They surface errors when you&#x27;re writing code instead of later, in production.</p>
<blockquote>
<p>If you&#x27;d like early access to thin events for v1 resources, please <a href="https://forms.gle/tgPakyrUakYoDm6L7">fill out this form</a>.</p>
</blockquote>
<h2>How it works today</h2>
<p>When handling thin events today, you need to write a lot of code to validate and route the event correctly. You&#x27;re also responsible for managing the Stripe-Context header when handling events for connected accounts.</p>
<p>Here&#x27;s what a TypeScript handler might look like today:</p>
<pre><code class="language-ts">const app = express();
const stripe = new Stripe(&quot;sk_test_YOUR_KEY_HERE&quot;);

app.post(
  &#x27;/webhook&#x27;,
  express.raw({type: &#x27;application/json&#x27;}),
  async (req, res) =&gt; {
    const sig = req.headers[&#x27;stripe-signature&#x27;]?.[0] ?? &#x27;&#x27;;

    try {
      const eventNotification = stripe.parseEventNotification(
        req.body,
        sig,
        webhookSecret
      );

      if (eventNotification.type == &#x27;v1.billing.meter.error_report_triggered&#x27;) {
        console.log(
          `Meter w/ id ${eventNotification.related_object.id} had a problem`
        );

        const event = await eventNotification.fetchEvent();
        console.log(`More info: ${event.data.developer_message_summary}`);
      } else if (eventNotification.type === &#x27;v1.billing.meter.no_meter_found&#x27;) {
        const meters = await stripe.billing.meters.list(undefined, {
          // important!
          stripeContext: eventNotification.context,
        });

        console.log(
          `Meter not found. Available meters for this account are: ${meters.data
            .map((m) =&gt; m.id)
            .join(&#x27;, &#x27;)}`
        );
        // ...
      } else {
        console.log(`Received unhandled event type: ${eventNotification.type}`);
      }

      res.sendStatus(200);
    } catch (err) {
      console.log(`Webhook Error: ${(err as any).stack}`);
      res.status(400).send(`Webhook Error: ${(err as any).message}`);
    }
  }
);
</code></pre>
<p>Notice how all of your business logic is interwoven in webhook-related hardware? Even if you split your logic into small functions, it&#x27;s still up to you to wire everything up correctly. Plus you have to hope (and verify) that you haven&#x27;t made any typos in those webhook type strings.</p>
<h2>A better way</h2>
<p>Event notification handlers abstract away as much of this grunt work as possible, keeping you focused on the unique parts of your implementation. The Stripe SDK takes care of all the casting, routing, validating, and conversions behind the scenes. Let&#x27;s walk through some examples.</p>
<h3>Create a handler</h3>
<p>The first step is initializing the handler itself. If you&#x27;re not using <code>StripeClient</code> yet, you can use it for just this bit of your integration without changing anything else. There&#x27;s more info <a href="https://docs.stripe.com/sdks/server-side#stripeclient">in our docs</a>.</p>
<p>Once you have an authenticated client, create your handler. Make sure to provide a &quot;fallback callback&quot;, the function that gets called when the event isn&#x27;t otherwise handled:</p>
<pre><code class="language-ts">const stripe = new Stripe(&quot;sk_test_YOUR_KEY_HERE&quot;);

const handler = stripe.notificationHandler(
  webhookSecret,
  async (unhandledEvent, client, details) =&gt; {
    console.log(
      `Received unhandled event type: ${unhandledEvent.type}; the SDK does${
        details.isKnownEventType ? &#x27;&#x27; : &#x27; not&#x27;
      } have types for it`
    );
  }
);
</code></pre>
<h3>Handle a thin event</h3>
<p>You can declare callback functions anywhere in your codebase:</p>
<pre><code class="language-ts">// webhookCallbacks.ts
const handleNoMeterFound = async (
  notification: Stripe.V2.Core.Events.V1BillingMeterNoMeterFoundEventNotification,
  client: Stripe
) =&gt; {
  // don&#x27;t need to specify stripeContext! it just works
  const meters = await client.billing.meters.list();

  console.log(
    `Meter not found. Available meters are: ${meters.data
      .map((m) =&gt; m.id)
      .join(&#x27;, &#x27;)}`
  );
};

// eventDestination.ts
import {handleNoMeterFound} from &#x27;./webhookCallbacks.ts&#x27;

const handler = stripe.notificationHandler(...) // same as above

handler.on(&#x27;v1.billing.meter.no_meter_found&#x27;, handleNoMeterFound);
</code></pre>
<p>That <code>handler.on(...)</code> call validates (at compile time) that the argument types for your callback match those expected for the <code>v1.billing.meter.no_meter_found</code> event, so you don&#x27;t have any surprises at runtime.</p>
<p>The SDK will supply the parsed event notification and a special instance of <code>StripeClient</code> that&#x27;s pre-bound to the context of the event. It also retains all the settings from the <code>client</code> instance that created the handler, so no need to re-configure anything. You can make any additional API calls you want.</p>
<p>Best of all, because <code>handleNoMeterFound</code> is just a function, you can test it as part of your normal development flow.</p>
<p>In some languages (like TypeScript), you can even declare callbacks inline, meaning they have the exact correct types without having to specify anything:</p>
<pre><code class="language-ts">handler.on(
  &#x27;v1.billing.meter.error_report_triggered&#x27;,
  async (eventNotification, client) =&gt; { // these are automatically typed correctly
    console.log(
      `Meter w/ id ${eventNotification.related_object.id} had a problem`
    );

    const event = await eventNotification.fetchEvent();
    console.log(`More info: ${event.data.developer_message_summary}`);
  }
);
</code></pre>
<h3>The final result</h3>
<p>Put it all together and you&#x27;ve got a much more maintainable event destination:</p>
<pre><code class="language-ts">const app = express();
const stripe = new Stripe(&quot;sk_test_YOUR_KEY_HERE&quot;);

const apiKey = process.env.STRIPE_API_KEY ?? &#x27;&#x27;;
const webhookSecret = process.env.WEBHOOK_SECRET ?? &#x27;&#x27;;

const handler = stripe.notificationHandler(
  webhookSecret,
  async (unhandledEvent, client, details) =&gt; {
    console.log(
      `Received unhandled event type: ${unhandledEvent.type}; the SDK does${
        details.isKnownEventType ? &#x27;&#x27; : &#x27; not&#x27;
      } have types for it`
    );
  }
);

handler.on(&quot;v1.billing.meter.no_meter_found&quot;, handleNoMeterFound);
handler.on(
  &quot;v1.billing.meter.error_report_triggered&quot;,
  handleErrorReportTriggered
);

app.post(
  &quot;/webhook&quot;,
  express.raw({ type: &quot;application/json&quot; }),
  async (req, res) =&gt; {
    try {
      const sig = req.headers[&quot;stripe-signature&quot;]?.[0] ?? &quot;&quot;;
      handler.handle(req.body, sig);
      res.sendStatus(200);
    } catch (err) {
      console.log(`Webhook Error: ${(err as any).stack}`);
      res.status(400).send(`Webhook Error: ${(err as any).message}`);
    }
  }
);
</code></pre>
<p>This pattern is supported in all seven of our <a href="https://docs.stripe.com/sdks/server-side">server-side SDKs</a>, though the exact implementation details will differ between them. In each case, we designed a clean and idiomatic experience for the language.</p>
<h2>What comes next</h2>
<p>While event notification handlers represent an exciting leap forward, we&#x27;re not done yet. This more structured approach to event handling sets the stage for even better experiences going forward. While we don&#x27;t have concrete plans yet, we&#x27;re working on better validations (like ensuring your handler is listening for all the events your webhook endpoint is sending) and better integrations with popular web frameworks so we can make the integration experience even more seamless.</p>
<h2>How you can help</h2>
<p>Ahead of their GA launch later this summer, <strong>we&#x27;re looking for developer feedback</strong> on the handlers to ensure they&#x27;re ready for prime time. Though we&#x27;re confident in their overall design, there&#x27;s no replacing adoption in a real production codebase. If you have a chance to try them, <strong>please report any feedback</strong> (good or bad) in your SDK&#x27;s corresponding GitHub issue:</p>
<ul>
<li><a href="https://github.com/stripe/stripe-node/issues/2763">Node</a></li>
<li><a href="https://github.com/stripe/stripe-python/issues/1835">Python</a></li>
<li><a href="https://github.com/stripe/stripe-java/issues/2239">Java</a></li>
<li><a href="https://github.com/stripe/stripe-go/issues/2379">Go</a></li>
<li><a href="https://github.com/stripe/stripe-ruby/issues/1895">Ruby</a></li>
<li><a href="https://github.com/stripe/stripe-php/issues/2087">PHP</a></li>
<li><a href="https://github.com/stripe/stripe-dotnet/issues/3404">.NET</a></li>
</ul>]]></content:encoded></item><item><title><![CDATA[Extending Stripe’s network with stablecoins]]></title><description><![CDATA[Most of the world's money still moves in batches. A wire submitted at 2pm might settle by end of day, if you made the cutoff. ACH takes one to three business days. Cross-border payments touch correspondent banks, each adding lag and a fee. Stripe has spent over a decade building the Global Payments and Treasury Network (GPTN), a programmable infrastructure for global money movement that batches transfers to reduce cost, nets opposing flows to minimize actual cash moved, and routes through the optimal path between accounts. Under the hood, it's a graph with accounts as nodes, and payment rails as edges.]]></description><link>https://stripe.dev/blog/extending-stripes-network-with-stablecoins</link><guid isPermaLink="true">https://stripe.dev/blog/extending-stripes-network-with-stablecoins</guid><dc:creator><![CDATA[Sai Samant]]></dc:creator><pubDate>Mon, 22 Jun 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Most of the world&#x27;s money still moves in batches. A wire submitted at 2pm might settle by end of day, if you made the cutoff. ACH takes one to three business days. Cross-border payments touch correspondent banks, each adding lag and a fee. Stripe has spent over a decade building the <a href="https://stripe.com/newsroom/news/stripe-expands-global-infrastructure-with-new-funding">Global Payments and Treasury Network</a> (GPTN), a programmable infrastructure for global money movement that batches transfers to reduce cost, nets opposing flows to minimize actual cash moved, and routes through the optimal path between accounts. Under the hood, it&#x27;s a graph with accounts as nodes, and payment rails as edges.</p>
<p>Stablecoins offered something new: a stablecoin transfer settles on-chain in seconds, 24/7, with global reach and no per-country rail integration. When we decided to add stablecoin support to Stripe, the challenge went beyond &quot;support a new currency.&quot; We had to stitch blockchain-based settlement into the GPTN&#x27;s existing graph of bank accounts, payment rails, and entity structures, while respecting safeguarding requirements and regional regulations, and making it work in both directions.</p>
<h2>How stablecoins move through Stripe</h2>
<p>Here&#x27;s a concrete example: a platform holds a USD balance on Stripe and wants to pay out a user in stablecoins.</p>
<p>When the platform calls our Payouts API, we decrement their USD balance and record a pending stablecoin obligation. Then, crucially, we don&#x27;t wait for fiat rails to catch up. We maintain a stablecoin liquidity pool at Bridge, our crypto infrastructure partner. Bridge can draw from this pool and credit the recipient&#x27;s wallet directly, delivering the payout in seconds to minutes.</p>
<p>Meanwhile, the dollars catch up asynchronously. Our cash management layer creates a request to move funds from the originating bank account to replenish the Bridge liquidity pool — but that movement happens over traditional fiat rails, and takes hours to days depending on the corridor. Opposing flows on the same route get batched and netted, so the number of actual bank transfers is a small fraction of the number of payouts.</p>
<p>The reverse direction — stablecoins back to fiat — works the same way. Bridge liquidates the stablecoins, dollars flow back through intercompany channels, and liquidity at the fiat entity provides immediate availability while cash settles.</p>
<p>Decoupling settlement and movement was our key insight. User-facing settlement runs at crypto speed, while the underlying cash movement runs at fiat speed. Liquidity pools on each side bridge the gap.</p>
<h2>The role of Bridge</h2>
<p>Bridge, which Stripe acquired in 2025, is the orchestration layer between Stripe&#x27;s fiat infrastructure and the blockchain. It manages wallet custody, executes on-chain transfers, and handles the minting and burning of stablecoins against reserve accounts. When we need to fund a recipient&#x27;s wallet, Bridge handles the last mile: converting from the liquidity pool to stablecoins and executing the on-chain transfer to the destination address and replenishing the pool with stablecoins when incoming fiat settles.</p>
<p>Because Stripe relies on Bridge for stablecoin custody and transactional services, every fiat-to-stablecoin conversion, in either direction, is a cross-entity cash movement. Payout and onramp transactions require cash to move from Stripe&#x27;s fiat entities to Bridge; offramps and stablecoin-to-fiat conversions bring it back. Each flow needs to be tracked as an intercompany liability, settled through intercompany channels, and reconciled across both sets of books.</p>
<p>The settlement graph had to integrate a new type of node — the wallet — with properties unlike any bank account it had handled before: near-instant finality, per-recipient (not pooled) balances, and no cutoff times.</p>
<h2>Extending the existing graph</h2>
<p>When we integrated stablecoins, we had two choices: build a parallel system, or extend the existing GPTN. We chose to extend.</p>
<p>The existing infrastructure already solves hard problems — orchestration, netting, cross-entity settlement, FX, reconciliation, and liquidity management — with years of investment behind them. Building stablecoins on top gave us those capabilities for free on day one, and improvements to shared infrastructure benefit both fiat and stablecoin flows.</p>
<p>To understand what we extended, it helps to see how the fiat side works. Stripe operates entities in dozens of countries, each with its own bank accounts. We model cash movement as a directed graph: nodes are accounts, edges are the available rails between them (ACH, SEPA, wire, book transfer), each with properties for currency, speed, cost, and availability. When the cash management layer needs to move money from account A to account B, it finds the optimal path through this graph.</p>
<p>At Stripe&#x27;s scale, this graph handles millions of cash movement requests per day. Many of them never result in an actual bank transfer — we apply net settlement, which cancels them out. If account A owes account B 10M USD, and account B owes account A 8M USD, we send one 2M USD wire from account A to account B instead of two. With multilateral netting across entity pairs, we can net out a significant portion of overall daily volume across our entities—but getting it right means carefully tracking when cash needs to be available at the destination.</p>
<p>Adding stablecoins meant adding new node types and edge types to this graph. The new nodes are wallets: Bridge&#x27;s liquidity pool, and individual user wallets for recipients. The new edges connect fiat accounts to these wallets, in both directions. The same netting logic applies across fiat and stablecoin flows — if we&#x27;re sending 5M USD to Bridge to fund payouts while 3M USD is flowing back from offramps, the engine nets this to a single 2M USD fiat transfer. Bilateral netting between fiat and stablecoin flows is one of our most important efficiency gains, because it directly reduces the number of cross-entity transfers needed to keep the whole system running.</p>
<p>The composability of this approach also makes expansion practical. Adding a new currency pair or product is a matter of extending a path through the graph, rather than building a new pipeline. A EUR → stablecoin flow, for example, naturally composes from capabilities that already exist: the FX leg converts EUR to USD through our treasury hub, and the stablecoin leg mints at Bridge. Stablecoin support for new products can reuse the same architecture rather than requiring bespoke infrastructure each time.</p>
<p>A USD → stablecoin payout starts with a standard fiat hop that already existed in the graph. The routing layer understands both fiat and wallet nodes, so it can construct end-to-end paths that cross the fiat-to-stablecoin boundary and complete the payout. Wallet edges will also eventually add routing redundancy: for example, a USD → MXN payout will be able to go via a traditional fiat partner or via Bridge (liquidating a stablecoin to MXN), giving the system multiple paths for the same funds and the ability to choose based on cost, speed, or availability.</p>
<h2>Making it instant</h2>
<p>The net settlement engine was designed for a world where everything is measured in hours. It runs on a planning cycle of a few minutes, a negligible delay when your fastest rail settles in hours.</p>
<p>Stablecoins changed that calculus. When a user is waiting for a stablecoin to land in their wallet, a few minutes of batching is the difference between &quot;instant&quot; and &quot;not instant.&quot; Some wallet funding requests are urgent — maybe a high-priority payout is pending — and holding them for the next batch isn&#x27;t acceptable.</p>
<p>We introduced a direct mode that bypasses the planning cycle for time-sensitive requests, explicitly skipping batching and netting opportunities in exchange for speed. A request that could have netted against an opposing flow now goes out as its own transfer. The system now runs both modes simultaneously, with most flows going through the standard cycle and benefiting from netting, and urgent wallet funding going direct. The decision of which mode to use is made upstream based on urgency, and in the future could factor in real-time pool state and demand forecasts to make smarter tradeoffs between speed and netting efficiency.</p>
<p>The liquidity pool is the tightest operational constraint in the system. Payouts and onramps reduce it; offramps and stablecoin-to-fiat conversions replenish it. On a day where payout volume and offramp volume are roughly balanced, the pool stays relatively stable with minimal fiat transfers needed. The netting engine makes this work: by netting opposing fiat-stablecoin flows before they hit the banking system, it directly reduces how much replenishment pressure ends up on fiat rails in the first place. Demand-driven forecasting — the ability to right-size the pool based on predicted flows in each direction—is one of our biggest near-term opportunities.</p>
<h2>Running 24/7</h2>
<p>Fiat settlement follows banking hours. Blockchains don&#x27;t, which has real operational consequences. With fiat, a problem detected in a batch process might give us hours to respond before users are affected. With stablecoins settling in minutes, our alerting SLAs tightened from hours to minutes. We designed routine operations like pool replenishment and reconciliation, to run without needing manual intervention outside business hours. We had to extend our operational tooling, too: the same dashboards that show bank account balances and sweep status now display wallet balances, pool utilization, and on-chain transaction status, with reconciliation that spans both bank statements and blockchain records.</p>
<h3>Agent-accessible operations</h3>
<p>As SLAs tighten further, autonomous agents that can run routine diagnostics start to make a lot of sense. We&#x27;re investing in making stablecoin operations more agent-accessible: giving agents visibility into the full transaction lifecycle, training them on patterns from past incidents, and enabling them to diagnose and respond to common issues without needing to wait for a human to come online.</p>
<h2>What&#x27;s next</h2>
<p>The infrastructure described in this post is live and handling real traffic, but we&#x27;re early in what stablecoins at Stripe will become. Here&#x27;s where we&#x27;re headed.</p>
<h3>Demand-driven liquidity forecasting</h3>
<p>Static pool buffers work, but they&#x27;re not capital-efficient. The next step is moving to dynamic, forecast-driven sizing: right-sizing liquidity per corridor per time window based on historical usage patterns, time-of-day demand curves, redemption rates, and real-time inflow/outflow rates. Better forecasting also makes smarter batch-vs-direct decisions by pre-positioning liquidity ahead of predicted demand spikes rather than reacting after the pool dips.</p>
<h3>Faster replenishment</h3>
<p>We&#x27;re working on using real-time fiat rails like RTP and SEPA Instant to replenish the liquidity pool where available. Closing the gap between pool drain speed (minutes) and replenishment speed (currently hours to days) is one of the highest-leverage improvements we can make to give us smaller buffers, less idle capital, and more resilience to demand spikes.</p>
<h3>Expanding to new products and corridors</h3>
<p>The composability of a settlement graph makes expansion practical. We can add stablecoins to a new product by connecting a new fiat leg to existing wallet edges, or add a new currency pair through the treasury hub&#x27;s FX capabilities. This same architecture now powers <a href="https://docs.stripe.com/global-payouts/send-money/link">stablecoin payouts to Link</a>, enabling companies like <a href="https://stripe.com/blog/everything-we-announced-at-sessions-2026">Meta</a> to pay content creators in dollars around the globe. Each of these are new edges in the graph—they all plug into the same netting, routing, reconciliation, and settlement infrastructure that already exists.</p>
<hr/>
<p>We want to make stablecoins a first-class rail at Stripe, alongside ACH, SEPA, and wires, as another edge type in the graph with its own speed and cost characteristics, available to every Stripe product that moves money. The architecture is designed to get us there. If these are the kinds of opportunities you want to work on, <a href="https://stripe.com/jobs">we&#x27;re hiring</a>.</p>]]></content:encoded></item><item><title><![CDATA[From hobby to real world: integrating the hard stuff with agents]]></title><description><![CDATA[When building with agents, most guides will give you the happy path - how to do something the best way, without friction. This post is all friction, and how to get through it when things go wrong.]]></description><link>https://stripe.dev/blog/integrating-services-with-agents-stripe-projects</link><guid isPermaLink="true">https://stripe.dev/blog/integrating-services-with-agents-stripe-projects</guid><dc:creator><![CDATA[Anna Spysz]]></dc:creator><pubDate>Tue, 16 Jun 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/integrating-services-with-agents-stripe-projects/wireframe.jpg"/><link rel="preload" as="image" href="https://stripe.dev/images/integrating-services-with-agents-stripe-projects/architecture.png"/><link rel="preload" as="image" href="https://stripe.dev/images/integrating-services-with-agents-stripe-projects/auth.png"/><link rel="preload" as="image" href="https://stripe.dev/images/integrating-services-with-agents-stripe-projects/leaderboard.png"/><p>Previously, I wrote about <a href="https://stripe.dev/blog/building-with-agents-stripe-projects">building a transcription app</a> using Stripe Projects. While that tool certainly has a real use case and is still being used by my team, it&#x27;s much closer to a hobby project than a real production app. That much was obvious both from how quickly I was able to spin it up (just a few days, with only a few hours of development time) to how frictionless the process was.</p>
<p>So what does building a more complex production app using agents look like? Even with the best-laid plans, sometimes it will still be mostly friction. Navigating that friction is where the takeaways come in.</p>
<h3>The use case</h3>
<p>So many people are building with <a href="https://projects.dev/">Stripe Projects</a>, it&#x27;s getting hard to keep track of them all! I wanted a fun way to catalog and showcase open-source projects, and maybe add a competitive element to it for future hackathons, so I decided to build a leaderboard app. The concept was fairly simple: I wanted to build an app where users could add their project to a table, share it via social media links, and vote on other projects.</p>
<p>Despite the simplicity of the app, it would require some more complex infrastructure, namely:</p>
<ul>
<li>Auth, for tracking user submissions and enforcing a one-project-per-user limit</li>
<li>A database with multiple tables, for tracking project details, user details, and votes</li>
<li>Hosting for the app itself</li>
<li>A mechanism to prevent voting abuse, i.e. ensuring non-logged-in users can only vote once per project</li>
</ul>
<h3>The initial plan</h3>
<p>When it comes to agents, a well-crafted plan pays dividends in the long run. I had my requirements but wanted to ensure nothing was missing, so I meticulously laid out every piece of functionality the leaderboard would need.</p>
<p>First, I made sure I had the Projects skill available, so my agent would know to use Projects for provisioning infrastructure:</p>
<pre><code class="language-bash">npx skills add https://docs.stripe.com
</code></pre>
<p>Then, I crafted a detailed markdown document covering requirements and styling, such as the theme&#x27;s primary and secondary colors, fonts, light/dark mode, etc. Anything left out of the requirements is left to the agent&#x27;s discretion, which tends to trend towards the generic, and I had a very specific vision in mind as well as Stripe UX guidelines to follow.</p>
<p>The requirements included how I was thinking about the data structure, and contained instructions such as:</p>
<pre><code class="language-markdown">- The leaderboard data will be stored in a database in an object with the following types:
    leaderboard: [
        {
            &quot;projectId&quot;: &quot;project_&lt;ID&gt;&quot;, &lt;taken from state.local.json&gt;
            &quot;projectName&quot;: &quot;demo-project&quot;, &lt;taken from state.local.json&gt;
            &quot;projectURL&quot;: &quot;&quot;, &lt;link to live deployed app, from form&gt;
            &quot;projectRepoURL&quot;: &quot;&quot;, &lt;link to public repo, from form&gt;
            &quot;projectStackURL&quot;: &quot;&quot;, &lt;from `stripe projects share` output, from form&gt;
            &quot;userName&quot;: &quot;&quot;, &lt;handle of user when registering&gt;
            &quot;userEmail&quot;: &quot;&quot;, &lt;given when registering, must match Stripe Projects email&gt;
            &quot;ranking&quot;: {
                &quot;position&quot;: &lt;number&gt;,
                &quot;upvotes&quot;: &lt;number&gt;,
                &quot;timeAtPosition&quot;: &lt;function&gt;
            }
        }
    ]
</code></pre>
<p>While in the past I&#x27;ve used apps such as Figma for wireframing, this time I figured the agent needed just a general concept for layout, and I was in a bit of a hurry - so I went old school and sketched out a rough idea of what I had in mind on a scrap of paper, took a photo, and added it to my requirements directory:</p>
<p><img src="https://stripe.dev/images/integrating-services-with-agents-stripe-projects/wireframe.jpg" alt="Unfortunately not on the back of a napkin, but close enough"/></p>
<p>Then came the actual prompt, using Claude&#x27;s planning mode:</p>
<pre><code class="language-bash">I want to build a single-page NextJS app that shows a leaderboard of applications created using Stripe Projects and allows users to upvote on existing applications without registering, though limited to one vote per user per project. In order to add an application to the leaderboard for voting, a user will need to register and fill out a form, which will be in a modal activated by a &quot;Submit your project&quot; button.

See &lt;inserted requirements&gt; for details, styling, and data requirements, and &lt;wireframe&gt; for a rough wireframe, which should be re-created as a table.

Use the best database and auth for the use case, though simpler to use would be better for both, and any other infrastructure needed. Use Vercel for hosting as I already have an account.
</code></pre>
<h3>The initial pivot</h3>
<p>As expected, the agent&#x27;s initial plan included provisioning the following resources:</p>
<ul>
<li>Database + auth: Supabase (single service)</li>
<li>Hosting: Vercel</li>
</ul>
<p>The agent followed my request for infrastructure simplicity and chose Supabase as both my database and auth provider, and Vercel for hosting as specified. It created CSS variables based on my requested color scheme, laid out the data structure for a <code>projects</code> table and a <code>votes</code> table, and presented the following architecture:</p>
<p><img src="https://stripe.dev/images/integrating-services-with-agents-stripe-projects/architecture.png" alt="Initial architecture from the agent"/></p>
<p>Everything looked good, and I hit <code>build</code>. The agent started by adding services via the CLI:</p>
<pre><code class="language-bash">stripe projects add supabase/project
stripe projects add vercel/project
stripe projects env --pull   # writes .env.local
</code></pre>
<p>This also added the relevant skills for each service, which let the agent know to install <code>@supabase/supabase-js</code> and other libraries we would need.</p>
<p>As the agent was building, we ran into the first issue: enforcing the one vote per user rule for voting on the leaderboard. Using the Vercel skill, the agent determined that the built-in rate limiter for voting integrity it had initially planned would not work on the serverless architecture we were using, and a server-side enforcement mechanism was necessary. It would replace the existing vote flow with a single atomic database function, but that required a new service: <a href="https://upstash.com/docs/redis/overall/getstarted">Upstash Redis</a> for rate limiting.</p>
<p>I gave my consent, it ran <code>stripe projects add upstash/redis</code>, and it was able to complete the rest of the build plan.</p>
<h3>Pivoting providers</h3>
<p>The real problems started once the integrations were configured, the app was working locally, and I started running into limits from the providers themselves. This was most obvious when I started testing the sign-up auth flow using <a href="https://supabase.com/docs/guides/auth">Supabase auth</a> - specifically, the email rate limits. It turns out that everybody has a plan until they get hit by rate limits.</p>
<p>Supabase&#x27;s auth plan includes a rate limitation of two emails that can be sent per hour from my project - that was not enough for testing, much less a production app. Digging into the docs, I found out I would have to set up a custom SMTP provider or use my own email service via a custom hook to update the email rate limit. That meant another service, specifically an SMTP or email provider, something not yet offered by Stripe Projects - as well as potential new costs. I wanted to avoid having to run a custom email service when all I needed was a way to authenticate my users, so I decided to swap out my auth provider instead.</p>
<p>This is the point where human intervention was absolutely necessary, because when asked for potential solutions to this issue, the agent suggested implementing a Twilio integration instead, which aside from lacking the functionality I needed would have been absolute overkill for my use case.</p>
<h3>Pivoting plans</h3>
<p>It was time to go back into plan mode, both for myself and the agent. I would need to research the available auth providers, see if any of them would work for my use case, or potentially use a provider not offered by Projects. Luckily, <a href="https://clerk.com/docs">Clerk</a> ended up having the functionality I needed with much more generous rate limits aimed at preventing abuse.</p>
<p>Once I knew what service I wanted to use, I asked the agent to come up with a migration plan, which resulted in this simplified auth flow:</p>
<p><img src="https://stripe.dev/images/integrating-services-with-agents-stripe-projects/auth.png" alt="The new auth flow"/></p>
<p>Once built, I was able to get auth working locally and in a deployed version, and after a few more back and forth iterations was ready to share the MVP.</p>
<h3>Takeaways</h3>
<h4>Think through your data and UX when planning</h4>
<p>The requirements doc I made had data models as well as CSS values laid out for the agent. Because I already had specific ideas of how I wanted those to look, writing them out for the initial plan saved a lot of the back and forth I had experienced in previous builds.</p>
<h4>Trust but verify (and research)</h4>
<p>The initial plan had a major flaw: an in-memory rate limiter doesn&#x27;t work on Vercel&#x27;s serverless architecture. Next time I&#x27;ll have an adversarial agent go over the plan to find issues like this before they come up in the build, as well as going through the provider&#x27;s docs if I have any doubts a proposed solution won&#x27;t work.</p>
<h4>Be deliberate in your prompt wording</h4>
<p>I specifically asked the agent &quot;Use the best database and auth for the use case, though simpler to use would be better for both&quot;. When it saw that Supabase offered both, it naturally picked that as the simplest solution, though it ended up being the wrong one for auth. Initially biasing the agent towards simplicity ended up causing a significant refactor after the first build was complete.</p>
<h4>Go with your gut</h4>
<p>When I initially hit the email rate limit issue, I did ask the agent to give me possible solutions; it came up with implementing Twilio on top of everything. My gut said this was an auth problem and changing the auth provider should solve it, and that ended up being the case.</p>
<h3>Results</h3>
<p>By the time I was finished, I had built a far more complex app with Projects than any previous endeavors, and ended up with five resources between four providers. But most importantly, I ended up with a working app:</p>
<p><img src="https://stripe.dev/images/integrating-services-with-agents-stripe-projects/leaderboard.png" alt="The finished leaderboard app"/></p>
<p>Before submitting my code for review, I switched to a different model and ran a security audit with one agent and a performance audit with another and patched some fairly trivial but important issues those agents found. All together, the leaderboard app took over a full day of building compared to just a few hours for my previous projects - which is worth putting into perspective considering a year ago it would have taken a whole team at least a week to put together.</p>
<p>If you want to get started with Stripe Projects yourself, head to <a href="https://projects.dev/">projects.dev</a>!</p>]]></content:encoded></item><item><title><![CDATA[How API changes flow into Stripe's developer products]]></title><description><![CDATA[When a Stripe engineer makes changes to an API, merging the PR is just the beginning. Stripe maintains a vast developer product suite that needs to stay in sync with the API changes we frequently ship:]]></description><link>https://stripe.dev/blog/how-api-changes-flow-into-stripes-developer-products</link><guid isPermaLink="true">https://stripe.dev/blog/how-api-changes-flow-into-stripes-developer-products</guid><dc:creator><![CDATA[Mikael Mello]]></dc:creator><pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate><content:encoded><![CDATA[<link rel="preload" as="image" href="https://stripe.dev/images/how-api-changes-flow-into-stripes-developer-products/image2.png"/><link rel="preload" as="image" href="https://stripe.dev/images/how-api-changes-flow-into-stripes-developer-products/image4.png"/><link rel="preload" as="image" href="https://stripe.dev/images/how-api-changes-flow-into-stripes-developer-products/image3.png"/><link rel="preload" as="image" href="https://stripe.dev/images/how-api-changes-flow-into-stripes-developer-products/image1.png"/><p>When a Stripe engineer makes changes to an API, merging the PR is just the beginning. Stripe maintains a vast developer product suite that needs to stay in sync with the API changes we <a href="https://docs.stripe.com/changelog">frequently ship</a>:</p>
<ul>
<li>The <a href="https://docs.stripe.com/sdks">official SDKs</a> need to have new versions published across seven different languages and three release channels.</li>
<li>The <a href="https://docs.stripe.com/stripe-cli">CLI</a>, <a href="https://docs.stripe.com/workbench/shell">Shell and API Explorer</a> need to be updated to include the new functionality.</li>
<li>The <a href="https://docs.stripe.com/api">API Reference</a> needs to reflect the new API shape, and have meaningful descriptions to assist developers in integrating with it.</li>
<li>The <a href="https://docs.stripe.com/changelog">public changelog</a> needs to list the new change, why it matters, and how it affects users.</li>
<li>Internal tools need to reflect these changes to help Stripe engineers (and AI agents) manage and maintain their APIs.</li>
<li>… and more.</li>
</ul>
<p>Keeping all of this in sync is a significant challenge, and building the infrastructure to do it reliably is a long-term investment that started a few years back.</p>
<h2>Enter OpenAPI</h2>
<p>The <a href="https://www.openapis.org/what-is-openapi">OpenAPI Specification</a> defines a standard format for describing HTTP APIs. It’s widely used across the industry to generate documentation, client libraries, and to build out a company’s <a href="https://kenneth.io/post/developer-experience-infrastructure-dxi">Developer Experience Infrastructure</a> (DXI). <a href="https://www.stainless.com/">Stainless</a> (founded by a former Stripe engineer and recently acquired by Anthropic) and many other companies are built around solving the challenge of DXI.</p>
<p>OpenAPI has been part of Stripe’s toolbox since 2016, initially powering internal tooling like validations and test suites. In 2017, we published a <a href="https://github.com/stripe/openapi">public OpenAPI description of our API</a> and kept it up-to-date as our APIs evolved. In 2019, we used OpenAPI to auto-generate a Stripe SDK for the first time, releasing stripe-java@8.0.</p>
<p>But OpenAPI wasn’t universally adopted across Stripe. Some tools relied on the OpenAPI descriptions we generated. Others, like the API Reference, bypassed OpenAPI entirely and parsed APIs straight from our internal Ruby-based <a href="https://en.wikipedia.org/wiki/Domain-specific_language">DSL</a>.</p>
<h3>A breaking point</h3>
<p>In 2024, Stripe introduced the <a href="https://docs.stripe.com/api-v2-overview">v2 namespace</a> to bring improved domain modeling and updated API semantics to our users. Internally, this meant an architectural shift in how we define and implement APIs, leveraging Protocol Buffers. Unlike v1, v2 APIs are defined by a separate internal DSL and serialized into snapshots using a Stripe-built internal API spec. Both are protobuf-based, which lets us share primitives and represent the DSL, API shape, and version differences (patch files) in the same format.</p>
<div class="sideBySideCode">
<div>
<pre><code class="language-rb">class AccountRetrieveMethod &lt; APICore::AbstractAPIMethod
  description &quot;Retrieves the details of an account.&quot;

  resource AccountAPIResource
  permission Permission.fetch(:account_retrieve)
  # ...

  client_metadata(
    in_class: &quot;AccountAPIResource&quot;,
    method_type: :retrieve,
    endpoint_url: &quot;/v1/accounts/:id&quot;
  )

  def execute
    # ...
  end
end
</code></pre>
</div>
<div>
<pre><code class="language-proto">service AccountsApi {
  // Retrieves the details of an Account.
  rpc RetrieveAccount(RetrieveAccountRequest) returns (RetrieveAccountResponse) {
    option (v2.method) = {
      get: &quot;/v2/core/accounts/:id&quot;
      documented: PUBLIC
      permission: &quot;v2_account_read&quot;
      error_code: &quot;not_found&quot;
      error_code: &quot;account_rate_limit_exceeded&quot;
      // ...
    };
  }
}
</code></pre>
</div>
</div>
<p>Now there were two formats: OpenAPI (or parsing the Ruby-based DSL) for v1, and our internal API spec for v2.</p>
<p>The consequences of the new internal API spec were immediate. Teams responsible for a developer product now had to support two formats instead of one. SDK and API Reference generators had to be duplicated. Preview support for v2 APIs in the CLI was shut down entirely because the maintenance burden was too high. Stripe Shell didn’t support v2 APIs at all.</p>
<p>The situation was already unsustainable, and we hadn&#x27;t even launched v2 publicly yet. With new developer products like <a href="https://docs.stripe.com/workflows">Stripe Workflows</a> and an improved changelog on the way, things were only going to get worse. It was clear we needed to converge on a single way of representing Stripe’s APIs.</p>
<p>Between the different DSLs, the internal API spec, and OpenAPI, the choice was clear: converge on OpenAPI. OpenAPI was, and still is, the industry standard for representing HTTP APIs and a path already proven within Stripe. Most importantly, OpenAPI is extensible. Anything we needed to represent that wasn’t natively supported could be added as an extension.</p>
<p>This decision to use OpenAPI enabled us to further expand a pipeline that ships developer products at scale, keeping them in sync with every API change we make.</p>
<h2>How the pipeline works</h2>
<p>The pipeline starts with generating OpenAPI descriptions for each API namespace. But not every consumer of those descriptions needs the same thing. The Stripe CLI doesn&#x27;t need the same information as the public API Reference. Internal tools can operate on APIs that are internal or still in development, which have no place in public-facing SDKs. To handle this, the generator’s output is controlled by three main parameters: variant, phase, and version. Together, they determine which API definitions (methods, event types, resource types, etc.) and what metadata is included in each artifact.</p>
<p><img src="https://stripe.dev/images/how-api-changes-flow-into-stripes-developer-products/image2.png" alt=""/></p>
<blockquote>
<p>The OpenAPI Generator takes inputs from both v1 and v2 DSLs and produces tailored artifacts per consumer.</p>
</blockquote>
<p>The variant controls which APIs and metadata are included for a given consumer. Think of variants as &quot;surfaces.&quot; Each developer product gets its own tailored artifact.</p>
<ul>
<li>Public artifacts, like our <a href="https://github.com/stripe/openapi">stripe/openapi</a> repository, exclude metadata that describe private or sensitive information.</li>
<li>Some APIs are omitted entirely from certain artifacts. For example, methods that power internal integrations like the Stripe Dashboard don&#x27;t appear in the descriptions powering public SDKs.</li>
</ul>
<p>The phase parameter filters APIs based on their <a href="https://docs.stripe.com/release-phases">release phase</a>:</p>





















<table><thead><tr><th style="text-align:left">Phase</th><th style="text-align:left">What&#x27;s included</th></tr></thead><tbody><tr><td style="text-align:left"><code>generally_available</code></td><td style="text-align:left">GA APIs only</td></tr><tr><td style="text-align:left"><code>public_preview</code></td><td style="text-align:left">GA + public preview APIs</td></tr><tr><td style="text-align:left"><code>private_preview</code></td><td style="text-align:left">GA + public preview + private preview APIs</td></tr></tbody></table>
<p>For example, our public API Reference is powered by two different artifacts chosen based on whether the Preview toggle is enabled–one for GA APIs and one for preview. That distinction is controlled at the DSL level, so that a method marked undocumented won&#x27;t appear in any public spec and one marked private_preview won&#x27;t appear in GA artifacts.</p>
<p>The version parameter dictates what version of Stripe&#x27;s API is being described. Products like <a href="https://docs.stripe.com/api">API Reference</a> or <a href="https://docs.stripe.com/data/database">Stripe Database</a> can support different views of the API depending on the API version chosen by the customer, and this parameter allows these teams to fetch the appropriate OpenAPI description.</p>
<p>In the v2 namespace, all API changes are stored on disk and easily reproducible from the beginning of history. This allows us to generate snapshots of the API surface, and their OpenAPI representation, for any point in time. With that capability, we can provide a canonical OpenAPI description for any past or current API version, and propagate improvements to the spec (e.g. richer metadata about an API) at will.</p>
<p>Things are a little trickier for v1. Due to the historical architecture of the v1 platform, it’s not trivial to regenerate descriptions for non-current API versions. Today, when a new API version is released, we store the current OpenAPI spec as the canonical description of v1 APIs for that version. It’s not perfect: new variants or metadata we add to the spec can&#x27;t be backfilled into older versions. This is a problem we still have to solve in v1: how to support older API versions and leverage new OpenAPI features we add.</p>
<h2>Making OpenAPI our own</h2>
<p>Stripe uses <a href="https://swagger.io/docs/specification/v3_0/openapi-extensions/">vendor extensions</a> to include extra information about the APIs and communicate to consumers how the spec should be interpreted. The extensions cover gaps in what the OpenAPI spec natively supports, helping us bridge the two API namespaces and their DSLs when possible, and signal to consumer products about special behavior that applies to some APIs but not others.</p>
<p>For example, we define Stripe&#x27;s resources as reusable JSON schemas (under <a href="https://learn.openapis.org/specification/components.html">#/components/schemas</a>) and include an extension called x-stripeOperations. This extension lists which API methods operate on a given resource and, with some additional metadata, directly powers the methods available on an SDK object and the subcommands available in the Stripe CLI.</p>
<pre><code class="language-yaml">components:
  schemas:
    v2.core.account:
      type: object
      properties: [...]
      x-stripeOperations:
      - method_name: list
        method_type: list
        operation: get
        path: /v2/core/accounts
      # ...
      - method_name: close
        method_type: custom
        operation: post
        path: &quot;/v2/core/accounts/{id}/close&quot;
</code></pre>
<p>Extensions helped us turn a standard spec into something purpose-built for Stripe&#x27;s needs. Here are a few others we have added over the years:</p>



































<table><thead><tr><th style="text-align:left">Extension Name</th><th style="text-align:left">Applies to</th><th style="text-align:left">Description</th></tr></thead><tbody><tr><td style="text-align:left"><code>x-resourceId</code></td><td style="text-align:left">Resource schemas</td><td style="text-align:left">String that represents the canonical name for that resource.</td></tr><tr><td style="text-align:left"><code>x-stripeResource</code></td><td style="text-align:left">Resource schemas</td><td style="text-align:left">Object containing type hints for consumers, e.g. the package location where the type should be added.</td></tr><tr><td style="text-align:left"><code>x-stripeEvent</code></td><td style="text-align:left">Event schemas</td><td style="text-align:left">Object containing additional metadata about an event, e.g. its type identifier or whether it represents a <a href="https://docs.stripe.com/event-destinations#benefits-of-thin-events">thin event</a>.</td></tr><tr><td style="text-align:left"><code>x-stripeAccess</code></td><td style="text-align:left">Operations or schemas</td><td style="text-align:left">Object containing metadata about the access and visibility of that API definition, used by the API Reference to conditionally render documentation for APIs that are not publicly available.</td></tr><tr><td style="text-align:left"><code>x-expandableFields</code></td><td style="text-align:left">v1 API Resource schemas</td><td style="text-align:left">Contains a list of names of fields that are expandable via the <code>expand</code> parameter. See <a href="https://docs.stripe.com/api#expanding_objects">expanding objects</a>.</td></tr></tbody></table>
<h2>Drawing the rest of the owl</h2>
<p>The above is just a small piece of the work we do to be able to deliver consistently world-class developer experiences. Over Stripe&#x27;s lifetime, we’ve invested significant care into understanding how API changes impact our entire product suite. Along the way, we’ve codified a set of design patterns that all APIs must follow.</p>
<p>Before a PR can merge, a set of automated tests validate API changes against those patterns: API naming, field types, versioning considerations such as backwards compatibility, or constraints specific to products (e.g. reserved words in SDKs). Documentation and code samples follow the same model, they&#x27;re authored and validated alongside the API definition itself and threaded through to the API Reference generator via OpenAPI artifacts.</p>
<pre><code class="language-sh">warn[empty_doc_string]: Every public API method, event, error, field, &amp; enum value should have a doc string. See https://go/v2-api-ref for more details.
  --&gt; /path/to/accounts_api_service.proto:71
70 |   
71 |   rpc CreateAccount (CreateAccountRequest) returns (CreateAccountResponse) {
72 |     option (v2.method) = {
73 |       stable_id: &quot;v2_method_create_account&quot;
74 |       post: &quot;/v2/core/accounts&quot;
   | ...
  help: see https://go/api-v2-lint/empty_doc_string for more information
</code></pre>
<p>In the PR view, we display an OpenAPI-based diff of the changes for visual (or much more common now: agent-assisted) spot-checking. In the v2 namespace, we also detect and display changes to past API versions (if applicable), a feature that has been especially helpful for catching inadvertent changes to older API versions.</p>
<p><img src="https://stripe.dev/images/how-api-changes-flow-into-stripes-developer-products/image4.png" alt=""/></p>
<p>Once the changes get merged, a pre-release flow is kicked off: the changes make their way into our internal API versioning tool, where the OpenAPI description of the previous and upcoming API versions are compared. Each change is listed and enriched with extra information such as previews of how SDKs will be affected, the affected product, suggested changelog descriptions, and more, providing one more place for product architects to review and refine upcoming changes.</p>
<p><img src="https://stripe.dev/images/how-api-changes-flow-into-stripes-developer-products/image3.png" alt=""/></p>
<p>At release time, the canonical OpenAPI description for the new version is snapshotted and propagated to downstream consumers for their subsequent releases. The new artifact is made available to internal services via direct dependencies or build artifacts. It’s published to our CDN and fetched by workflows in our open-source repos such as <a href="https://github.com/stripe/openapi">stripe/openapi</a> and <a href="https://github.com/stripe/stripe-cli">stripe/stripe-cli</a>. The API Reference is updated with the new API version, up-to-date code snippets are generated, new versions of our SDKs and the CLI are released, and so on.</p>
<p><img src="https://stripe.dev/images/how-api-changes-flow-into-stripes-developer-products/image1.png" alt=""/></p>
<p>All of those improvements–and many others not mentioned here–have helped Stripe maintain a high bar for developer products while reducing manual work for engineers and the chance of mistakes being made.</p>
<h2>What comes next</h2>
<p>The pipeline’s foundation is solid, but there’s a lot more we want to build on top of it.</p>
<p>Smoothing the differences between defining v1 and v2 APIs is an area of active work as we incorporate the learnings of both stacks to provide a more unified experience to Stripe engineers building on either namespace (or most likely, both!). We are also extending our DSLs so that product teams can more easily define custom integration points targeting any developer product: some functionality may not really fit Stripe&#x27;s API, but make a lot of sense in the Stripe CLI or as an action in a Stripe Workflow.</p>
<p>As Stripe launches new developer products, such as <a href="https://docs.stripe.com/data/database">Stripe Database</a>, we constantly find new use-cases that fit into this pipeline and managing that growth becomes its own challenge. It takes a coordinated effort maintaining parity between the DSLs, the OpenAPI generators of both v1 and v2 stacks, and keeping consumer teams in the loop when further extending the spec.</p>
<p>—</p>
<p>There&#x27;s no shortage of interesting problems left to solve. If that sounds like your kind of challenge, and you want your work to reach every developer who builds on Stripe, <a href="https://stripe.com/jobs">we&#x27;re hiring</a>.</p>]]></content:encoded></item><item><title><![CDATA[Scaling up your microservice testing with Apache Spark—Part 2]]></title><description><![CDATA[Some microservices are difficult to test because their behavior depends on a long tail of inputs that are hard to model by hand. This post focuses on how to build a repeatable Apache Spark replay harness for testing without creating a second implementation of the service.]]></description><link>https://stripe.dev/blog/microservice-testing-with-apache-spark-part-2</link><guid isPermaLink="true">https://stripe.dev/blog/microservice-testing-with-apache-spark-part-2</guid><dc:creator><![CDATA[Vivek Yadav]]></dc:creator><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Scaling up your microservice testing with Apache Spark]]></title><description><![CDATA[Some microservices are difficult to test because their behavior depends on a long tail of inputs that are hard to model by hand. For that class of problem, Apache Spark's massive parallelism and linear scalability can be leveraged to build a regression test harness. That makes it possible to compare old and new implementations, model upcoming rule changes, and quantify impact before a change reaches production.]]></description><link>https://stripe.dev/blog/microservice-testing-with-apache-spark</link><guid isPermaLink="true">https://stripe.dev/blog/microservice-testing-with-apache-spark</guid><dc:creator><![CDATA[Vivek Yadav]]></dc:creator><pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Compliance versus conversion: Tax ID validation at checkout]]></title><description><![CDATA[For product-led businesses, conversion is paramount, so an important consideration is deciding whether to implement synchronous or asynchronous tax ID validation in a checkout flow. One is friendlier for conversion, but the other can keep your business compliant.]]></description><link>https://stripe.dev/blog/tax-id-validation-at-checkout</link><guid isPermaLink="true">https://stripe.dev/blog/tax-id-validation-at-checkout</guid><dc:creator><![CDATA[Jake Estes]]></dc:creator><pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Modern Java at Stripe: Language upgrades as a service]]></title><description><![CDATA[We all know the stereotypes of working at enterprise tech companies: endless bureaucracy and behemoth codebases running on a years-old language version. Most organizations know they should modernize quickly, but haven’t figured out how to build an upgrade path compatible with a large codebase. The recurring cost of upgrading keeps most large JVM codebases trailing behind the latest Java versions.]]></description><link>https://stripe.dev/blog/modern-java-at-stripe-language-upgrades-as-a-service</link><guid isPermaLink="true">https://stripe.dev/blog/modern-java-at-stripe-language-upgrades-as-a-service</guid><dc:creator><![CDATA[Mahib Hosain]]></dc:creator><pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[What it feels like building with Stripe Projects]]></title><description><![CDATA[Building a GitHub traffic dashboard with Stripe Projects revealed a surprising shift from “AI coding” into real infrastructure orchestration. In under an hour, Auth0, Supabase, Vercel, Prisma, deployments, environments, and scheduled jobs materialized around an idea with remarkable speed. The experience highlighted both the power and the emerging challenge of AI-assisted infrastructure: operational visibility, ownership clarity, environment management, and understanding rapidly orchestrated systems.]]></description><link>https://stripe.dev/blog/what-it-feels-like-building-with-stripe-projects</link><guid isPermaLink="true">https://stripe.dev/blog/what-it-feels-like-building-with-stripe-projects</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[You can't whisper at an AI agent]]></title><description><![CDATA[What a dozen steering experiments taught us about how LLM-based agents actually behave in the wild.]]></description><link>https://stripe.dev/blog/ai-steering-experiments</link><guid isPermaLink="true">https://stripe.dev/blog/ai-steering-experiments</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Learnings from init to deploy]]></title><description><![CDATA[This post is a follow-up to last week’s “From init to deploy: building with agents and Stripe Projects” and explores the learnings gained along the way.]]></description><link>https://stripe.dev/blog/learnings-from-building-with-agents-stripe-projects</link><guid isPermaLink="true">https://stripe.dev/blog/learnings-from-building-with-agents-stripe-projects</guid><dc:creator><![CDATA[Anna Spysz]]></dc:creator><pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[From init to deploy: building with agents and Stripe Projects]]></title><description><![CDATA[Stripe Projects makes it easy to spin up a useful, complex application that uses various SaaS providers. This post describes how one of our developer advocates built an internal transcript editing application using Stripe Projects, Vercel, and OpenRouter.]]></description><link>https://stripe.dev/blog/building-with-agents-stripe-projects</link><guid isPermaLink="true">https://stripe.dev/blog/building-with-agents-stripe-projects</guid><dc:creator><![CDATA[Anna Spysz]]></dc:creator><pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Formatting an entire 25 million line codebase overnight: the rubyfmt story]]></title><description><![CDATA[This is the story of how our Developer Productivity team extended and rolled out rubyfmt, our Rust-based zero-config, ultra-fast autoformatter across the world's largest Ruby codebase.]]></description><link>https://stripe.dev/blog/formatting-an-entire-25-million-line-codebase-overnight-the-rubyfmt-story</link><guid isPermaLink="true">https://stripe.dev/blog/formatting-an-entire-25-million-line-codebase-overnight-the-rubyfmt-story</guid><dc:creator><![CDATA[Fable Tales]]></dc:creator><pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[How we built Stripe Credits: A programmable, auditable way to pay your Stripe fees]]></title><description><![CDATA[In this post we’ll dive into how we built a virtual cashless payment method that works for prepaid and Stripe-issued credits, and cleanly integrates with our accounting, compliance, and other frameworks.]]></description><link>https://stripe.dev/blog/how-we-built-stripe-credits-a-programmable-auditable-way-to-pay-your-stripe-fees</link><guid isPermaLink="true">https://stripe.dev/blog/how-we-built-stripe-credits-a-programmable-auditable-way-to-pay-your-stripe-fees</guid><dc:creator><![CDATA[Pratik Gupta]]></dc:creator><pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Using AI agents to build Stripe-Salesforce integrations faster]]></title><description><![CDATA[Building payment and revenue integrations across Stripe and Salesforce has a steep learning curve spanning both platforms. CLI-based AI coding agents make this faster, but hallucinate on complex, evolving platform surfaces. Open-source grounding context files flatten the learning curve and reduce hallucination, letting developers ship working integrations sooner.]]></description><link>https://stripe.dev/blog/build-stripe-salesforce-integrations-faster-with-agents</link><guid isPermaLink="true">https://stripe.dev/blog/build-stripe-salesforce-integrations-faster-with-agents</guid><dc:creator><![CDATA[Vishnu Samavedula]]></dc:creator><pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Selective Test Execution at Stripe: Fast CI for a 50M-line Ruby monorepo]]></title><description><![CDATA[Stripe's Selective Test Execution system employs some clever tricks to allow us to continue scaling our team and our codebase while only running around 5% of our tests on average. Find out how it works!]]></description><link>https://stripe.dev/blog/selective-test-execution-at-stripe-fast-ci-for-a-50m-line-ruby-monorepo</link><guid isPermaLink="true">https://stripe.dev/blog/selective-test-execution-at-stripe-fast-ci-for-a-50m-line-ruby-monorepo</guid><dc:creator><![CDATA[Aditya Anchuri]]></dc:creator><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Provision a production-ready dev stack from your terminal]]></title><description><![CDATA[Provision hosting, databases, auth, analytics, AI and other dev tools from the Stripe CLI. Stripe Projects creates real resources in your own provider accounts, securely syncs agent-ready credentials, supports upgrades to paid tiers, and handles payments without leaving the terminal.]]></description><link>https://stripe.dev/blog/production-ready-dev-stack-from-terminal</link><guid isPermaLink="true">https://stripe.dev/blog/production-ready-dev-stack-from-terminal</guid><dc:creator><![CDATA[Rami Banna]]></dc:creator><pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Designing flexible payment flows with Checkout Session]]></title><description><![CDATA[Learn how to build a future-proof, global-ready checkout by implementing Stripe Checkout Session with embeddable Elements for flexible payments, tax calculation, and multi-currency support.]]></description><link>https://stripe.dev/blog/designing-flexible-payment-flows-with-checkoutsessions</link><guid isPermaLink="true">https://stripe.dev/blog/designing-flexible-payment-flows-with-checkoutsessions</guid><dc:creator><![CDATA[Allison Farris]]></dc:creator><pubDate>Fri, 06 Mar 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Building a mental model for Stripe payments]]></title><description><![CDATA[Learn how Stripe payments work under the hood by understanding the PaymentIntent lifecycle as a state machine—from checkout and tokenization to authorization, capture, and settlement—and why webhooks are essential for keeping your systems in sync with asynchronous payment state changes. ]]></description><link>https://stripe.dev/blog/building-a-mental-model-for-stripe-payments</link><guid isPermaLink="true">https://stripe.dev/blog/building-a-mental-model-for-stripe-payments</guid><dc:creator><![CDATA[Allison Farris]]></dc:creator><pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Minions: Stripe’s one-shot, end-to-end coding agents—Part 2]]></title><description><![CDATA[Minions are Stripe’s homegrown coding agents, responsible for more than a thousand pull requests merged each week. Though humans review the code, minions write it from start to finish. Learn how they work, and how we built them.]]></description><link>https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2</link><guid isPermaLink="true">https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2</guid><dc:creator><![CDATA[Alistair Gray]]></dc:creator><pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Minions: Stripe’s one-shot, end-to-end coding agents]]></title><description><![CDATA[Minions are Stripe’s homegrown coding agents, responsible for more than a thousand pull requests merged each week. Though humans review the code, minions write it from start to finish. Learn how they work, and how we built them.]]></description><link>https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents</link><guid isPermaLink="true">https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents</guid><dc:creator><![CDATA[Alistair Gray]]></dc:creator><pubDate>Mon, 09 Feb 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Configuring Stripe using Terraform and AI agents]]></title><description><![CDATA[Learn why using AI agents to author Terraform code is safer than direct API calls for Stripe configuration. Get transparent, consistent, and auditable infrastructure with code review workflows.]]></description><link>https://stripe.dev/blog/ai-agents-terraform-stripe-infrastructure</link><guid isPermaLink="true">https://stripe.dev/blog/ai-agents-terraform-stripe-infrastructure</guid><dc:creator><![CDATA[Michael Selander]]></dc:creator><pubDate>Tue, 27 Jan 2026 00:00:00 GMT</pubDate></item><item><title><![CDATA[Versioning in Stripe Workflows: Ship workflow changes with confidence]]></title><description><![CDATA[Versioning in Stripe Workflows lets you edit automations safely with drafts, roll back changes instantly, and see exactly what ran. Update with confidence.]]></description><link>https://stripe.dev/blog/introducing-versioning-for-stripe-workflows</link><guid isPermaLink="true">https://stripe.dev/blog/introducing-versioning-for-stripe-workflows</guid><dc:creator><![CDATA[Tanya Boiteau]]></dc:creator><pubDate>Wed, 10 Dec 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Building production-ready Stripe subscriptions using Kiro powers]]></title><description><![CDATA[Learn how to build production-ready Stripe subscriptions faster using Kiro Powers. Implement secure payment flows, webhooks, and scalable architecture with AI assistance.]]></description><link>https://stripe.dev/blog/building-production-ready-stripe-subscriptions-kiro-powers</link><guid isPermaLink="true">https://stripe.dev/blog/building-production-ready-stripe-subscriptions-kiro-powers</guid><dc:creator><![CDATA[Kalyani Koppisetti]]></dc:creator><pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Stablecoin payments for Stripe developers: zero crypto knowledge required]]></title><description><![CDATA[Learn how to add stablecoin payments to your Stripe integration in under an hour. Accept USDC on Ethereum, Solana, Polygon, and Base with zero blockchain knowledge—just add 'crypto' to your existing payment methods.]]></description><link>https://stripe.dev/blog/using-stripe-stablecoin-payments-no-crypto-knowledge</link><guid isPermaLink="true">https://stripe.dev/blog/using-stripe-stablecoin-payments-no-crypto-knowledge</guid><dc:creator><![CDATA[Robert Love]]></dc:creator><pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Using a web-based POS with a mobile Terminal reader]]></title><description><![CDATA[Learn how to integrate web-based POS systems with mobile Terminal readers using a React Native wrapper app. Connect M2 readers and Tap to Pay seamlessly.]]></description><link>https://stripe.dev/blog/web-pos-mobile-terminal-integration</link><guid isPermaLink="true">https://stripe.dev/blog/web-pos-mobile-terminal-integration</guid><dc:creator><![CDATA[Benjamin Nuttin]]></dc:creator><pubDate>Thu, 23 Oct 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[How we built it: Real-time analytics for Stripe Billing]]></title><description><![CDATA[Among global business leaders surveyed, 84% agree that adapting pricing quickly will be a key competitive advantage. Our new real-time analytics system for Stripe Billing helps them spot customer trends just as they emerge.]]></description><link>https://stripe.dev/blog/how-we-built-it-real-time-analytics-for-stripe-billing</link><guid isPermaLink="true">https://stripe.dev/blog/how-we-built-it-real-time-analytics-for-stripe-billing</guid><dc:creator><![CDATA[Reed Trevelyan]]></dc:creator><pubDate>Tue, 16 Sep 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Seamlessly connect Stripe events to your frontend]]></title><description><![CDATA[Learn how to connect Stripe events to your frontend applications using real-time subscriptions, webhooks, and AWS services. Build responsive payment UIs that react instantly to transaction updates, subscription changes, and account modifications with event-driven architecture.]]></description><link>https://stripe.dev/blog/seamlessly-connect-stripe-events-to-your-frontend</link><guid isPermaLink="true">https://stripe.dev/blog/seamlessly-connect-stripe-events-to-your-frontend</guid><dc:creator><![CDATA[Josh Kahn]]></dc:creator><pubDate>Thu, 11 Sep 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Workflows: Automate compliance safeguards by flagging transactions]]></title><description><![CDATA[Learn how to automate compliance safeguards with Stripe Workflows by flagging high-value transactions and suspicious orders to enhance risk management.]]></description><link>https://stripe.dev/blog/workflows-automate-compliance-safeguards</link><guid isPermaLink="true">https://stripe.dev/blog/workflows-automate-compliance-safeguards</guid><dc:creator><![CDATA[Ashley Ansari]]></dc:creator><pubDate>Thu, 11 Sep 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Marketplace monetization with Stripe]]></title><description><![CDATA[Complete guide to monetizing online marketplaces with Stripe Connect. Configure transaction fees, usage-based billing, and automated invoicing with code examples.]]></description><link>https://stripe.dev/blog/marketplace-monetization-with-stripe</link><guid isPermaLink="true">https://stripe.dev/blog/marketplace-monetization-with-stripe</guid><dc:creator><![CDATA[Ana Andres]]></dc:creator><pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Real-time vs batch reconciliation: Practical patterns for keeping data in sync]]></title><description><![CDATA[Learn effective data reconciliation strategies to keep product information synchronized between your internal systems and Stripe. Explore real-time vs batch processing tools, validation techniques, and best practices for maintaining consistent data across multiple platforms as your business scales.]]></description><link>https://stripe.dev/blog/database-reconciliation-growing-businesses-part-3</link><guid isPermaLink="true">https://stripe.dev/blog/database-reconciliation-growing-businesses-part-3</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Sat, 23 Aug 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[From naive webhooks to durable sync: Queue-based and event-driven data reconciliation patterns]]></title><description><![CDATA[Learn advanced database reconciliation patterns for Stripe integration. Explore queue-based architectures, event-driven sync, and robust error handling to maintain product data consistency at scale.]]></description><link>https://stripe.dev/blog/database-reconciliation-growing-businesses-part-2</link><guid isPermaLink="true">https://stripe.dev/blog/database-reconciliation-growing-businesses-part-2</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Fri, 22 Aug 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Keeping product data aligned with Stripe as your systems scale]]></title><description><![CDATA[Learn how product data synchronization between internal databases and payment providers like Stripe evolves from simple scripts to complex architectural challenges as your business scales from hundreds to millions of products.]]></description><link>https://stripe.dev/blog/database-reconciliation-growing-businesses-part-1</link><guid isPermaLink="true">https://stripe.dev/blog/database-reconciliation-growing-businesses-part-1</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Thu, 21 Aug 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Gating entrances with Stripe and NFC passes]]></title><description><![CDATA[Learn how to gate physical entrances using Stripe subscriptions and NFC passes. This guide shows how to issue Apple/Google Wallet passes with PassNinja, integrate Stripe Checkout, and control door access with a Raspberry Pi and NFC reader.]]></description><link>https://stripe.dev/blog/gating-entrances-with-stripe-and-nfc-passes</link><guid isPermaLink="true">https://stripe.dev/blog/gating-entrances-with-stripe-and-nfc-passes</guid><dc:creator><![CDATA[Joshua Nussbaum]]></dc:creator><pubDate>Thu, 21 Aug 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Guardrails for money movement: Integrating Stripe MCP with Portia AI]]></title><description><![CDATA[Learn how to build secure AI agents for payment processing using Stripe MCP and Portia AI, with built-in guardrails and human approval workflows.]]></description><link>https://stripe.dev/blog/guardrails-money-movement-integrating-stripe-mcp-portia-ai</link><guid isPermaLink="true">https://stripe.dev/blog/guardrails-money-movement-integrating-stripe-mcp-portia-ai</guid><dc:creator><![CDATA[Mounir Mouawad]]></dc:creator><pubDate>Wed, 13 Aug 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Building financial operations agents with Hypermode and Stripe]]></title><description><![CDATA[Learn how to build sophisticated financial operations agents using natural language with Hypermode and Stripe. Create AI agents that handle payments, process refunds, generate invoices, and automate complex financial workflows—no coding required.]]></description><link>https://stripe.dev/blog/building-financial-operations-agents-hypermode-stripe</link><guid isPermaLink="true">https://stripe.dev/blog/building-financial-operations-agents-hypermode-stripe</guid><dc:creator><![CDATA[William Lyon]]></dc:creator><pubDate>Wed, 06 Aug 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Workflows: Automatically customize an object with metadata]]></title><description><![CDATA[Workflow patterns provide repeatable solutions to common developer problems. This pattern helps automate customize a Stripe object with metadata.]]></description><link>https://stripe.dev/blog/workflows-customize-objects-metadata</link><guid isPermaLink="true">https://stripe.dev/blog/workflows-customize-objects-metadata</guid><dc:creator><![CDATA[Ashley Ansari]]></dc:creator><pubDate>Tue, 22 Jul 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[How we built it: Jurisdiction resolution for Stripe Tax]]></title><description><![CDATA[Our new jurisdiction resolution system (JRS) is a faster, less resource-intensive solution to the challenging problem of determining tax obligations in places with complicated, overlapping tax jurisdictions.]]></description><link>https://stripe.dev/blog/how-we-built-it-jurisdiction-resolution-for-stripe-tax</link><guid isPermaLink="true">https://stripe.dev/blog/how-we-built-it-jurisdiction-resolution-for-stripe-tax</guid><dc:creator><![CDATA[Erich Rentz]]></dc:creator><pubDate>Thu, 10 Jul 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Workflows: Creating early fraud alerts for streamlined refunds]]></title><description><![CDATA[Workflow patterns provide repeatable solutions to common developer problems. This pattern helps automate creating early fraud alerts when streamlining refunds.]]></description><link>https://stripe.dev/blog/workflows-creating-early-fraud-alerts-for-streamlined-refunds</link><guid isPermaLink="true">https://stripe.dev/blog/workflows-creating-early-fraud-alerts-for-streamlined-refunds</guid><dc:creator><![CDATA[Ashley Ansari]]></dc:creator><pubDate>Thu, 10 Jul 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Stay within limits: API rate-limit-friendly pattern for Stripe webhooks]]></title><description><![CDATA[Learn how to build a resilient, rate-limit-friendly system for handling Stripe webhooks at scale. This guide explains the fetch-before-process pattern, its risks under high volume, and how to use Hookdeck to queue and throttle webhooks—ensuring reliable processing without exceeding Stripe API limits.]]></description><link>https://stripe.dev/blog/stay-within-limits-api-rate-limit-friendly-pattern-for-stripe-webhooks</link><guid isPermaLink="true">https://stripe.dev/blog/stay-within-limits-api-rate-limit-friendly-pattern-for-stripe-webhooks</guid><dc:creator><![CDATA[Phil Leggetter]]></dc:creator><pubDate>Thu, 03 Jul 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Stripe for marketplaces: Mapping commercial relationships in code]]></title><description><![CDATA[Learn how to use Stripe Connect to power your marketplace. Configure your fund flows, onboard merchants and manage payments in a few lines of code.]]></description><link>https://stripe.dev/blog/stripe-marketplaces-mapping-commercial-relationships-code</link><guid isPermaLink="true">https://stripe.dev/blog/stripe-marketplaces-mapping-commercial-relationships-code</guid><dc:creator><![CDATA[Ana Andres]]></dc:creator><pubDate>Thu, 03 Jul 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Using Connect embedded components to streamline your Connect onboarding flow]]></title><description><![CDATA[Replace hosted onboarding with embedded Connect components. Keep users on your platform while collecting requirements and enabling payments..]]></description><link>https://stripe.dev/blog/connect-embedded-components-streamline-onboarding</link><guid isPermaLink="true">https://stripe.dev/blog/connect-embedded-components-streamline-onboarding</guid><dc:creator><![CDATA[Jorge Aguirre Gonzalez]]></dc:creator><pubDate>Wed, 02 Jul 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Implementing scalable metered billing with Stripe: How Edgee handles billions of events]]></title><description><![CDATA[Discover how Edgee built a scalable, usage-based billing system processing billions of requests across 100+ global edge locations. Learn how they leverage Stripe’s metering and billing APIs to ensure accurate, resilient invoicing in a complex distributed architecture, all while solving critical web analytics data loss challenges.]]></description><link>https://stripe.dev/blog/implementing-scalable-metered-billing-with-stripe-how-edgee-handles-billions-of-events</link><guid isPermaLink="true">https://stripe.dev/blog/implementing-scalable-metered-billing-with-stripe-how-edgee-handles-billions-of-events</guid><dc:creator><![CDATA[Alex Casalboni]]></dc:creator><pubDate>Tue, 24 Jun 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Stop juggling multiple POS devices with Stripe Terminal]]></title><description><![CDATA[Eliminate separate devices and manual data entry. Stripe Terminal's on-screen input collection consolidates customer interactions in one place.]]></description><link>https://stripe.dev/blog/stripe-terminal-on-screen-input-collection</link><guid isPermaLink="true">https://stripe.dev/blog/stripe-terminal-on-screen-input-collection</guid><dc:creator><![CDATA[Andrew Robinson]]></dc:creator><pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Extending Docusign with Stripe to automate complex billing workflows]]></title><description><![CDATA[Discover how to streamline your agreement process by connecting DocuSign with Stripe using extension apps. Learn to build automated billing workflows without complex coding.]]></description><link>https://stripe.dev/blog/docusign-stripe-integration-automating-billing-workflows</link><guid isPermaLink="true">https://stripe.dev/blog/docusign-stripe-integration-automating-billing-workflows</guid><dc:creator><![CDATA[Paige Rossi]]></dc:creator><pubDate>Mon, 02 Jun 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[How to easily charge customers across accounts in an organization]]></title><description><![CDATA[Learn how to simplify customer charging across multiple accounts in your organization using Stripe's new sharing feature.]]></description><link>https://stripe.dev/blog/how-to-charge-customers-across-accounts</link><guid isPermaLink="true">https://stripe.dev/blog/how-to-charge-customers-across-accounts</guid><dc:creator><![CDATA[Daniel Wood]]></dc:creator><pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Introducing Stripe Workflows: Tailoring Payments to Your Business Needs]]></title><description><![CDATA[Today, Stripe launches Stripe Workflows, a powerful new way to automate your payment operations and orchestrate Stripe products with ease—right from the Dashboard. Whether you are looking to streamline invoicing, manage fraud detection, automate subscription logic, or build custom quote-to-cash flows, Stripe Workflows helps developers move faster with fewer lines of code. With built-in support for real-time triggers, conditional logic, and seamless integration across Stripe’s ecosystem, Workflows eliminates the need for managing infrastructure or writing boilerplate logic. Instead, you get a visual, event-driven automation engine designed for high-performance financial operations at scale. In this post, we’ll explore how Stripe Workflows can help you simplify complexity, boost developer productivity, and unlock new use cases—all with low latency and high reliability.]]></description><link>https://stripe.dev/blog/introducing-stripe-workflows</link><guid isPermaLink="true">https://stripe.dev/blog/introducing-stripe-workflows</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Introducing Stripe's new public preview release channel]]></title><description><![CDATA[Discover the benefits of Stripe's new public preview release channel, designed to provide early access to cutting-edge API features before their general availability. Start innovating faster with Stripe’s latest offerings while sharing your feedback to shape future developments!]]></description><link>https://stripe.dev/blog/introducing-stripes-new-public-preview-release-channel</link><guid isPermaLink="true">https://stripe.dev/blog/introducing-stripes-new-public-preview-release-channel</guid><dc:creator><![CDATA[Paul Anderson]]></dc:creator><pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Sessions 2025 Developer Track resources]]></title><description><![CDATA[Discover the latest from Stripe Sessions! Explore new products, APIs, and enhancements, including Stripe Workflows for no-code automation, MCP server for AI integration, and Accounts v2 for streamlined management.]]></description><link>https://stripe.dev/blog/sessions-2025-dev-track-resources</link><guid isPermaLink="true">https://stripe.dev/blog/sessions-2025-dev-track-resources</guid><dc:creator><![CDATA[Ashley Ansari]]></dc:creator><pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Optimizing Stripe API performance in Lambda with caching strategies]]></title><description><![CDATA[Discover advanced caching strategies to optimize Stripe API performance in AWS Lambda using Amazon ElastiCache and DynamoDB. Learn how to manage API rate limits, reduce latency, and minimize costs while ensuring data consistency and scalability in high-volume applications.]]></description><link>https://stripe.dev/blog/optimizing-stripe-api-performance-lambda-caching-elasticache-dynamodb</link><guid isPermaLink="true">https://stripe.dev/blog/optimizing-stripe-api-performance-lambda-caching-elasticache-dynamodb</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Mon, 21 Apr 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Because nobody likes being charged twice]]></title><description><![CDATA[In complex, high-volume systems, even minor failures—like a dropped internet connection—can lead to major headaches, such as duplicate charges. This post explores advanced patterns for integrating Stripe into your enterprise applications with a focus on building fault-tolerant, user-friendly payment systems. Learn how strategies like idempotency and message queues can protect your users from double charges, reduce operational errors, and improve reliability as your system scales.]]></description><link>https://stripe.dev/blog/because-nobody-likes-being-charged-twice</link><guid isPermaLink="true">https://stripe.dev/blog/because-nobody-likes-being-charged-twice</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Using an AWS microservice architecture for subscription management]]></title><description><![CDATA[Explore the complexities of enterprise subscription management and discover how an AWS microservice architecture can streamline subscription lifecycles. This guide uses AWS services to create a scalable, reliable, and auditable subscription management system, addressing challenges like mid-cycle plan changes and usage-based billing adjustments.]]></description><link>https://stripe.dev/blog/aws-microservice-architecture-subscription-management</link><guid isPermaLink="true">https://stripe.dev/blog/aws-microservice-architecture-subscription-management</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Unlocking powerful analytics with Stripe Data Pipeline and Google BigQuery]]></title><description><![CDATA[Discover how to integrate Stripe Data Pipeline with Google Cloud Storage and BigQuery for powerful analytics. This guide walks you through setting up automated data transfers, optimizing reporting, and unlocking valuable insights to enhance your business decisions.]]></description><link>https://stripe.dev/blog/unlock-powerful-analytics-stripe-data-pipeline-bigquery</link><guid isPermaLink="true">https://stripe.dev/blog/unlock-powerful-analytics-stripe-data-pipeline-bigquery</guid><dc:creator><![CDATA[Sushant Jain]]></dc:creator><pubDate>Fri, 21 Mar 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Stripe’s new AI Assistant in VS Code]]></title><description><![CDATA[Learn about the Stripe AI assistant in VS Code and how we built it, including technical details on retrieval, evaluations, measurement, and more.]]></description><link>https://stripe.dev/blog/stripes-ai-assistant-vs-code</link><guid isPermaLink="true">https://stripe.dev/blog/stripes-ai-assistant-vs-code</guid><dc:creator><![CDATA[Mathew Varughese]]></dc:creator><pubDate>Tue, 18 Mar 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Real-time payment analytics: Building a data pipeline from Stripe to AWS]]></title><description><![CDATA[Discover how to build a scalable real-time payment analytics pipeline from Stripe to AWS. This guide explores the challenges, architectural components, and implementation details to help businesses monitor transactions, enhance security, and gain insights into customer behavior.]]></description><link>https://stripe.dev/blog/real-time-payment-analytics-stripe-to-aws-data-pipeline</link><guid isPermaLink="true">https://stripe.dev/blog/real-time-payment-analytics-stripe-to-aws-data-pipeline</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Fri, 14 Mar 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Load balancing Stripe API calls from multiple AWS regions]]></title><description><![CDATA[Implementing a resilient multi-region payment processing system using AWS and Stripe ensures reliable webhook handling, minimizes outages, and complies with regulations. This guide covers architecture design, challenges, and AWS service integration for optimal global performance and rate limit management.]]></description><link>https://stripe.dev/blog/load-balancing-stripe-api-calls-multiple-aws-regions</link><guid isPermaLink="true">https://stripe.dev/blog/load-balancing-stripe-api-calls-multiple-aws-regions</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Importing sales data from Stripe into AWS]]></title><description><![CDATA[This article focuses on how to process Stripe's data in your AWS account with minimal code. You'll learn how to query your business and customer data using SQL, assisted by an LLM (Large Language Model) for query generation. Additionally, it demonstrates how to enhance the security of your data integration by using the native Stripe-AWS integration, specifically the Stripe Event Destination to Amazon EventBridge feature.]]></description><link>https://stripe.dev/blog/importing-sales-data-from-stripe-into-aws</link><guid isPermaLink="true">https://stripe.dev/blog/importing-sales-data-from-stripe-into-aws</guid><dc:creator><![CDATA[Hidetaka Okamoto]]></dc:creator><pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Building serverless usage notification with AWS]]></title><description><![CDATA[In this article, we will guide you on how to set up usage threshold alerts based on consumption for customers with pay-as-you-go plans. By integrating AWS's serverless tools with Stripe, you can create a streamlined and easy-to-manage notification system with minimal coding.]]></description><link>https://stripe.dev/blog/building-serverless-usage-notification-with-aws</link><guid isPermaLink="true">https://stripe.dev/blog/building-serverless-usage-notification-with-aws</guid><dc:creator><![CDATA[Hidetaka Okamoto]]></dc:creator><pubDate>Thu, 27 Feb 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Securing Stripe API Keys in AWS with automatic rotation]]></title><description><![CDATA[Learn how to securely manage and automatically rotate your Stripe API keys in AWS for a production-grade approach. This guide covers best practices, tools, and strategies to enhance the security of your financial transactions.]]></description><link>https://stripe.dev/blog/securing-stripe-api-keys-aws-automatic-rotation</link><guid isPermaLink="true">https://stripe.dev/blog/securing-stripe-api-keys-aws-automatic-rotation</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Tracking customer spend in an omnichannel or multiprocessor environment]]></title><description><![CDATA[Discover how to effectively track customer spending across multiple channels and payment processors. Learn about the challenges of fragmented payment systems and explore solutions like the Payment Account Reference (PAR) to gain a unified view.]]></description><link>https://stripe.dev/blog/tracking-customer-spend-omnichannel-multiprocessor-environment</link><guid isPermaLink="true">https://stripe.dev/blog/tracking-customer-spend-omnichannel-multiprocessor-environment</guid><dc:creator><![CDATA[Andrew Robinson]]></dc:creator><pubDate>Wed, 19 Feb 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Building rock-solid Stripe integrations: A developer's guide to success]]></title><description><![CDATA[Learn about Stripe integrations with essential tips for robust, secure payment systems and seamless user experiences. This guide prepares developers for real-world scenarios, from subscription management to dispute resolution, ensuring secure and seamless payments.]]></description><link>https://stripe.dev/blog/building-solid-stripe-integrations-developers-guide-success</link><guid isPermaLink="true">https://stripe.dev/blog/building-solid-stripe-integrations-developers-guide-success</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Thu, 06 Feb 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Building resilient webhook handlers in AWS: Implementing DLQs for Stripe events]]></title><description><![CDATA[Discover how to build reliable webhook handlers for Stripe events using AWS in this comprehensive guide. Learn about the challenges of processing webhook events at scale and how to address them with an enterprise-grade architecture.]]></description><link>https://stripe.dev/blog/building-resilient-webhook-handlers-aws-dlqs-stripe-events</link><guid isPermaLink="true">https://stripe.dev/blog/building-resilient-webhook-handlers-aws-dlqs-stripe-events</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Thu, 30 Jan 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[New to Stripe? Learn the key concepts for software developers.]]></title><description><![CDATA[Discover the key concepts and terms for integrating Stripe in your applications. This guide covers Payment Intents Customer management, Subscription billing, Webhooks, Disputes, Refunds, and the benefits of expanded responses, providing developers with the tools they need to streamline payment processing efficiently.]]></description><link>https://stripe.dev/blog/new-to-stripe-learn-key-concepts-software-developers</link><guid isPermaLink="true">https://stripe.dev/blog/new-to-stripe-learn-key-concepts-software-developers</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Wed, 29 Jan 2025 00:00:00 GMT</pubDate></item><item><title><![CDATA[Crush errors with Sandbox testing]]></title><description><![CDATA[With Sandboxes, you can reproduce and troubleshoot errors in your integration without impacting live transactions.]]></description><link>https://stripe.dev/blog/crush-errors-with-sandbox-testing</link><guid isPermaLink="true">https://stripe.dev/blog/crush-errors-with-sandbox-testing</guid><dc:creator><![CDATA[David Edoh-Bedi]]></dc:creator><pubDate>Thu, 19 Dec 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Testing Connect onboarding with Sandboxes]]></title><description><![CDATA[As a platform, you can accelerate your Connect development with Sandboxes, by replicating your live settings to test out the merchant onboarding flow.]]></description><link>https://stripe.dev/blog/testing-connect-onboarding-with-sandboxes</link><guid isPermaLink="true">https://stripe.dev/blog/testing-connect-onboarding-with-sandboxes</guid><dc:creator><![CDATA[David Edoh-Bedi]]></dc:creator><pubDate>Thu, 19 Dec 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Developing a modern architecture for energy utilities with embedded finance]]></title><description><![CDATA[This blog explores how utilities can enhance customer engagement and capture energy-related revenue by creating an online marketplace.]]></description><link>https://stripe.dev/blog/developing-modern-architecture-energy-utilities-embedded-finance</link><guid isPermaLink="true">https://stripe.dev/blog/developing-modern-architecture-energy-utilities-embedded-finance</guid><dc:creator><![CDATA[Rajan Patel]]></dc:creator><pubDate>Tue, 17 Dec 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[How do I store inventory data in my Stripe application]]></title><description><![CDATA[Explore the critical importance of managing real-time inventory updates for both online marketplaces and physical stores. This blog post details the development of an event-driven architecture designed to synchronize inventory levels with Stripe payment events using AWS cloud services. The demonstration centers around the DevRel Swag Store, showcasing a practical application used at the GOTO Chicago event, which integrates serverless technologies like Amazon DynamoDB, AWS Lambda, and IoT Core for efficient inventory management. Learn about the challenges of using Stripe metadata for inventory, and discover robust solutions for handling race conditions and implementing dynamic inventory checks. Additionally, understand the trade-offs between using Stripe Payment Links and custom payment processes for real-time stock validation.]]></description><link>https://stripe.dev/blog/how-do-i-store-inventory-data-in-my-stripe-application</link><guid isPermaLink="true">https://stripe.dev/blog/how-do-i-store-inventory-data-in-my-stripe-application</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Mon, 16 Dec 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Japan community highlights: Effective testing and security]]></title><description><![CDATA[This article highlights development tips for efficiently developing and operating Stripe at a lower cost, as presented by the Japanese Stripe user community JP_Stripes. We'll focus on content from two events held in September 2024 in Aizuwakamatsu, Fukushima Prefecture, and Sapporo, Hokkaido.]]></description><link>https://stripe.dev/blog/japan-community-highlights-2024-09</link><guid isPermaLink="true">https://stripe.dev/blog/japan-community-highlights-2024-09</guid><dc:creator><![CDATA[Hidetaka Okamoto]]></dc:creator><pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Enhance your monitoring by integrating Stripe events with AWS CloudWatch Log Groups]]></title><description><![CDATA[Integrating Stripe events with AWS CloudWatch Log Groups enhances monitoring by enabling real-time tracking and analysis of Stripe transactions. This setup allows for seamless transfer of Stripe data to CloudWatch via Amazon EventBridge, streamlining payment operations. It provides the tools to visualize, monitor, and respond to events efficiently, optimizing strategic decisions.]]></description><link>https://stripe.dev/blog/enhance-your-monitoring-by-integrating-stripe-events-with-aws-cloudwatch-log-groups</link><guid isPermaLink="true">https://stripe.dev/blog/enhance-your-monitoring-by-integrating-stripe-events-with-aws-cloudwatch-log-groups</guid><dc:creator><![CDATA[Andrew Robinson]]></dc:creator><pubDate>Mon, 09 Dec 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Data access patterns for simple Stripe integrations]]></title><description><![CDATA[Is your Stripe integration ready to scale with your application? In this blog post, explore smart data strategies to enhance performance and security. Learn how to leverage Stripe/'s features, secure web backends, and serverless functions for efficient data management. Discover when to integrate a global CDN and use a separate database for deeper data control, ensuring a seamless user experience.]]></description><link>https://stripe.dev/blog/data-access-patterns-for-simple-stripe-Integrations</link><guid isPermaLink="true">https://stripe.dev/blog/data-access-patterns-for-simple-stripe-Integrations</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Wed, 27 Nov 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Managing multiple Stripe test environments from your AWS-hosted application]]></title><description><![CDATA[Stripe sandboxes enhance test environment management, offering improved functionality over test mode, allowing isolated accounts, multiple developers, and better access control while integrating seamlessly with AWS applications.]]></description><link>https://stripe.dev/blog/managing-multiple-stripe-test-environments-from-aws</link><guid isPermaLink="true">https://stripe.dev/blog/managing-multiple-stripe-test-environments-from-aws</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Tue, 26 Nov 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Getting started with Stripe in the UAE: A comprehensive guide for developers]]></title><description><![CDATA[Stripe has emerged as a vital payment processing platform in the UAE, offering diverse features like automated billing, fraud protection, and multi-currency support, empowering developers to streamline e-commerce operations.]]></description><link>https://stripe.dev/blog/getting-started-with-stripe-in-the-uae</link><guid isPermaLink="true">https://stripe.dev/blog/getting-started-with-stripe-in-the-uae</guid><dc:creator><![CDATA[Soad Abuelnaga]]></dc:creator><pubDate>Mon, 25 Nov 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Using demo data for testing Stripe integrations in AWS-hosted applications]]></title><description><![CDATA[This post discusses integrating the Stripe agent toolkit with large language models (LLMs) to enhance automation workflows, enabling financial services access, metered billing, and streamlined operations across agent frameworks.]]></description><link>https://stripe.dev/blog/using-demo-data-for-testing-stripe-integrations-in-aws</link><guid isPermaLink="true">https://stripe.dev/blog/using-demo-data-for-testing-stripe-integrations-in-aws</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Fri, 22 Nov 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Avoiding silent errors in your Stripe integration]]></title><description><![CDATA[Once your Stripe integration is live, it’s easy to set it and forget it. You can move on and focus on other elements of your application. However, trouble may be brewing behind the scenes. Unless you’ve set up robust logging and alerting in your application you may not be aware of increasing Stripe API error rates which could impact your bottom line]]></description><link>https://stripe.dev/blog/avoiding-silent-errors</link><guid isPermaLink="true">https://stripe.dev/blog/avoiding-silent-errors</guid><dc:creator><![CDATA[Abe Haskins]]></dc:creator><pubDate>Thu, 21 Nov 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Adding payments to your LLM agentic workflows]]></title><description><![CDATA[This post discusses integrating the Stripe agent toolkit with large language models (LLMs) to enhance automation workflows, enabling financial services access, metered billing, and streamlined operations across agent frameworks.]]></description><link>https://stripe.dev/blog/adding-payments-to-your-agentic-workflows</link><guid isPermaLink="true">https://stripe.dev/blog/adding-payments-to-your-agentic-workflows</guid><dc:creator><![CDATA[Steve Kaliski]]></dc:creator><pubDate>Thu, 14 Nov 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Developing and investigating subscription data flow]]></title><description><![CDATA[This article shows you how to use Stripe's sandbox to simplify your development process. You'll learn to create isolated test environments, simulate real-world scenarios, and debug your subscription logic efficiently. ]]></description><link>https://stripe.dev/blog/developing-and-investigating-subscription-data-flow</link><guid isPermaLink="true">https://stripe.dev/blog/developing-and-investigating-subscription-data-flow</guid><dc:creator><![CDATA[Hidetaka Okamoto]]></dc:creator><pubDate>Wed, 13 Nov 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Growing your Stripe integration With Event Destinations]]></title><description><![CDATA[Event Destinations has begun rolling out to general availability as of 10/30 and will soon be enabled for all accounts. This post explores how to use Stripe Event Destinations and Amazon EventBridge to achieve near infinite customization possibilities for your payment solutions without disrupting your existing integrations.]]></description><link>https://stripe.dev/blog/growing-your-stripe-integration-with-event-destinations</link><guid isPermaLink="true">https://stripe.dev/blog/growing-your-stripe-integration-with-event-destinations</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Thu, 31 Oct 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Managing SaaS access control with Stripe’s Entitlements API]]></title><description><![CDATA[This post introduces how to manage SaaS feature entitlements efficiently using the Stripe API. It explains why it's important to provide multiple plans to your customers and how to differentiate each plan through entitlement management. With the Stripe API, managing entitlements can become more straightforward, allowing you to focus on building and improving your core services.]]></description><link>https://stripe.dev/blog/managing-saas-access-control-with-stripe-entitlements-api</link><guid isPermaLink="true">https://stripe.dev/blog/managing-saas-access-control-with-stripe-entitlements-api</guid><dc:creator><![CDATA[Hidetaka Okamoto]]></dc:creator><pubDate>Thu, 31 Oct 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Preparing for Stripe API upgrades]]></title><description><![CDATA[New Stripe API versions come with new, and sometimes breaking, changes which should be evaluated before upgrading. Stripe Sandboxes and Workbench provide tooling to create isolated workspaces to test applications against these new changes.]]></description><link>https://stripe.dev/blog/prepare-for-api-upgrades</link><guid isPermaLink="true">https://stripe.dev/blog/prepare-for-api-upgrades</guid><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Thu, 24 Oct 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Optimize payment flow while reducing code complexity with Stripe's A/B Testing]]></title><description><![CDATA[This post explores how to leverage Stripe's A/B testing, its benefits, and how you can use it to boost online sales. You can learn how to improve the conversion rate by optimizing payment options without adding further code.]]></description><link>https://stripe.dev/blog/optimize-payment-flow-reduce-complexity-stripe-ab-testing</link><guid isPermaLink="true">https://stripe.dev/blog/optimize-payment-flow-reduce-complexity-stripe-ab-testing</guid><dc:creator><![CDATA[Hidetaka Okamoto]]></dc:creator><pubDate>Wed, 23 Oct 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Easily debug your 3DS authentication with Stripe Workbench]]></title><description><![CDATA[In this article, you'll learn how to investigate the payment process with just a few clicks on the Stripe dashboard. You'll also see how to obtain event data for testing code related to the 3DS authentication flow.]]></description><link>https://stripe.dev/blog/easily-debug-your-3ds-authentication-with-stripe-workbench</link><guid isPermaLink="true">https://stripe.dev/blog/easily-debug-your-3ds-authentication-with-stripe-workbench</guid><dc:creator><![CDATA[Hidetaka Okamoto]]></dc:creator><pubDate>Tue, 22 Oct 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Creating reusable developer environments with Stripe Sandboxes]]></title><description><![CDATA[When onboarding new team members, getting their development environments setup can be very time consuming. Preprovisioned environments with Stripe sandboxes and development containers can speed up their time to productivity.]]></description><link>https://stripe.dev/blog/creating-reusable-dev-environments-with-sandboxes</link><guid isPermaLink="true">https://stripe.dev/blog/creating-reusable-dev-environments-with-sandboxes</guid><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Sun, 20 Oct 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Testing subscriptions with Stripe Test Clocks and Workbench]]></title><description><![CDATA[Validating payment logic that gets triggered over a period of time is challenging and sometimes results in inelegant solutions.The combination of test clocks and workbench will improve the ability for developers to build and debug recurring payments.]]></description><link>https://stripe.dev/blog/testing-subscriptions-with-stripe-test-clocks-and-workbench</link><guid isPermaLink="true">https://stripe.dev/blog/testing-subscriptions-with-stripe-test-clocks-and-workbench</guid><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Mon, 14 Oct 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Choosing the right sandbox strategy for your organization]]></title><description><![CDATA[Stripe Sandboxes offer a powerful solution for isolating testing environments, but how you implement them can make all the difference. Pick a sandbox strategy that best suits your organizations development approach and team size.]]></description><link>https://stripe.dev/blog/choosing-the-right-sandbox-strategy-for-your-organization</link><guid isPermaLink="true">https://stripe.dev/blog/choosing-the-right-sandbox-strategy-for-your-organization</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Thu, 26 Sep 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Upgrading your Stripe plugin security]]></title><description><![CDATA[Secure your Stripe integrations by ditching unrestricted secret keys—learn how to protect merchant accounts with restricted access API keys and OAuth 2.0, and avoid compliance fees.]]></description><link>https://stripe.dev/blog/upgrading-your-stripe-plugin-security</link><guid isPermaLink="true">https://stripe.dev/blog/upgrading-your-stripe-plugin-security</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Thu, 19 Sep 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Avoiding test mode tangles with Stripe Sandboxes]]></title><description><![CDATA[Discover how to create independent testing environments with Stripe Sandboxes, streamline your development through GitHub automation, and prioritize the security of your API keys to enhance your payment integration process.]]></description><link>https://stripe.dev/blog/avoiding-test-mode-tangles-with-stripe-sandboxes</link><guid isPermaLink="true">https://stripe.dev/blog/avoiding-test-mode-tangles-with-stripe-sandboxes</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Mon, 16 Sep 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Advanced error handling patterns for Stripe enterprise developers]]></title><description><![CDATA[This post demonstrates some more advanced patterns to help you build resilient and robust payment systems to integrate Stripe with your enterprise applications. As your integration grows in complexity and volume, these patterns become crucial for maintaining system stability and providing a smooth user experience.]]></description><link>https://stripe.dev/blog/advanced-error-handling-patterns-for-Stripe-enterprise-developers</link><guid isPermaLink="true">https://stripe.dev/blog/advanced-error-handling-patterns-for-Stripe-enterprise-developers</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Tue, 03 Sep 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Simple error handling strategies with Stripe Workbench]]></title><description><![CDATA[With Workbench, developers now have a bird’s-eye view of their integration which shows many potential issues in one place. This makes it easier to see the impact of each incident, see how often it’s happening, and receive actionable advice for resolving the issue.]]></description><link>https://stripe.dev/blog/simple-error-handling-strategies-with-stripe-workbench</link><guid isPermaLink="true">https://stripe.dev/blog/simple-error-handling-strategies-with-stripe-workbench</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Mon, 02 Sep 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Stripe Developer Digest (August 2024): Workbench is now available to everyone]]></title><description><![CDATA[Test Stripe functionality in an isolated environment]]></description><link>https://stripe.dev/blog/stripe-developer-digest-august-2024-workbench-is-now-available-to-everyone</link><guid isPermaLink="true">https://stripe.dev/blog/stripe-developer-digest-august-2024-workbench-is-now-available-to-everyone</guid><dc:creator><![CDATA[Stripe Team]]></dc:creator><pubDate>Fri, 30 Aug 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Bringing your Stripe objects to life with Workbench]]></title><description><![CDATA[This post shows how to use the Stripe Workbench Inspector to examine the lifecycle of a PaymentIntent object.]]></description><link>https://stripe.dev/blog/bringing-your-stripe-objects-to-life-with-workbench</link><guid isPermaLink="true">https://stripe.dev/blog/bringing-your-stripe-objects-to-life-with-workbench</guid><dc:creator><![CDATA[Ben Smith]]></dc:creator><pubDate>Thu, 29 Aug 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Debugging your Stripe Invoicing integration with Workbench]]></title><description><![CDATA[With Stripe Invoicing, you can create and manage invoices for one-time and recurring payments. Whether caused by infrastructure issues or coding bugs, integration failures do sometimes happen, which can prevent invoices from being paid in a timely manner.]]></description><link>https://stripe.dev/blog/debugging-your-stripe-invoicing-integration-with-workbench</link><guid isPermaLink="true">https://stripe.dev/blog/debugging-your-stripe-invoicing-integration-with-workbench</guid><dc:creator><![CDATA[David Edoh-Bedi]]></dc:creator><pubDate>Thu, 29 Aug 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Peeking under the hood of Stripe Invoicing]]></title><description><![CDATA[Stripe Invoicing offers a no-code solution for sending invoices to customers. Because this option handles the complexity of all underlying API calls, developers sometimes struggle to understand the different phases a Stripe invoice goes through, which is problematic when attempting to debug payment failures. ]]></description><link>https://stripe.dev/blog/peeking-under-the-hood-of-stripe-invoicing</link><guid isPermaLink="true">https://stripe.dev/blog/peeking-under-the-hood-of-stripe-invoicing</guid><dc:creator><![CDATA[David Edoh-Bedi]]></dc:creator><pubDate>Mon, 26 Aug 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Observing immediate versus delayed payments with Stripe Workbench]]></title><description><![CDATA[Immediate and delayed payments need to be handled differently within an integration. It’s important to understand what events to listen for and properties to inspect for relevant changes.]]></description><link>https://stripe.dev/blog/observing-immediate-versus-delayed-payments-with-stripe-workbench</link><guid isPermaLink="true">https://stripe.dev/blog/observing-immediate-versus-delayed-payments-with-stripe-workbench</guid><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Fri, 23 Aug 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Debugging your AWS/Stripe integration just got easier]]></title><description><![CDATA[For developers building on AWS, you have various choices for processing payments within your application. Most developers choose a payment processing service to handle this part of their application flow, which involves integrating with a third-party vendor outside of the AWS environment.]]></description><link>https://stripe.dev/blog/debugging-your-aws-stripe-integration-just-got-easier</link><guid isPermaLink="true">https://stripe.dev/blog/debugging-your-aws-stripe-integration-just-got-easier</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Wed, 21 Aug 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Resolving production issues in your AWS/Stripe integration using Workbench]]></title><description><![CDATA[This blog shows how to find when something is wrong in production, avoid jumping between tabs/docs to find information, and resolving issues quickly in the troubleshooting process, using an AWS integration as a starting point.]]></description><link>https://stripe.dev/blog/resolving-production-issues-in-your-aws-stripe-integration-using-workbench</link><guid isPermaLink="true">https://stripe.dev/blog/resolving-production-issues-in-your-aws-stripe-integration-using-workbench</guid><dc:creator><![CDATA[James Beswick]]></dc:creator><pubDate>Wed, 21 Aug 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Doing more with less: Reducing requests to the Stripe API]]></title><description><![CDATA[The performance of an application can directly affect the customer experience. Using the expand feature along with caching techniques, developers can improve the performance of their application by reducing the number of API calls to Stripe.]]></description><link>https://stripe.dev/blog/doing-more-with-less-reducing-requests-to-the-stripe-api</link><guid isPermaLink="true">https://stripe.dev/blog/doing-more-with-less-reducing-requests-to-the-stripe-api</guid><dc:creator><![CDATA[Cecil Phillip]]></dc:creator><pubDate>Mon, 19 Aug 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Stripe Developer Digest (June 2024): Join the Sandboxes private beta]]></title><description><![CDATA[Test Stripe functionality in an isolated environment]]></description><link>https://stripe.dev/blog/stripe-developer-digest-june-2024-join-the-sandboxes-private-beta</link><guid isPermaLink="true">https://stripe.dev/blog/stripe-developer-digest-june-2024-join-the-sandboxes-private-beta</guid><dc:creator><![CDATA[Stripe Team]]></dc:creator><pubDate>Sun, 30 Jun 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[How Stripe’s document databases supported 99.999% uptime with zero-downtime data migrations ]]></title><description><![CDATA[In this blog post we’ll share an overview of Stripe’s database infrastructure and discuss the design and application of the Data Movement Platform.]]></description><link>https://stripe.dev/blog/how-stripes-document-databases-supported-99.999-uptime-with-zero-downtime-data-migrations</link><guid isPermaLink="true">https://stripe.dev/blog/how-stripes-document-databases-supported-99.999-uptime-with-zero-downtime-data-migrations</guid><dc:creator><![CDATA[Jimmy Morzaria]]></dc:creator><pubDate>Thu, 06 Jun 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Test clocks: How we made it easier to test Stripe Billing integrations]]></title><description><![CDATA[Stripe Billing allows businesses to manage customer relationships with recurring payments, usage triggers, and other customizable features.]]></description><link>https://stripe.dev/blog/test-clocks-how-we-made-it-easier-to-test-stripe-billing-integrations</link><guid isPermaLink="true">https://stripe.dev/blog/test-clocks-how-we-made-it-easier-to-test-stripe-billing-integrations</guid><dc:creator><![CDATA[Ji Huang]]></dc:creator><pubDate>Thu, 09 May 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Shepherd: How Stripe adapted Chronon to scale ML feature development ]]></title><description><![CDATA[This blog discusses the technical details of how we built Shepherd and how we are expanding the capabilities of Chronon to meet Stripe’s scale.]]></description><link>https://stripe.dev/blog/shepherd-how-stripe-adapted-chronon-to-scale-ml-feature-development</link><guid isPermaLink="true">https://stripe.dev/blog/shepherd-how-stripe-adapted-chronon-to-scale-ml-feature-development</guid><dc:creator><![CDATA[Ben Mears]]></dc:creator><pubDate>Mon, 15 Apr 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[Ledger: Stripe’s system for tracking and validating money movement ]]></title><description><![CDATA[Technical details on how Stripe built Ledger, a state-of-the-art money movement tracking system, including how teams at Stripe interact with the data quality metrics that underlie our payment processing network.]]></description><link>https://stripe.dev/blog/ledger-stripe-system-for-tracking-and-validating-money-movement</link><guid isPermaLink="true">https://stripe.dev/blog/ledger-stripe-system-for-tracking-and-validating-money-movement</guid><dc:creator><![CDATA[Ilya Ganelin]]></dc:creator><pubDate>Fri, 16 Feb 2024 00:00:00 GMT</pubDate></item><item><title><![CDATA[How we built it: Stripe Radar]]></title><description><![CDATA[What makes Radar, Stripe’s fraud prevention solution, so powerful? Here are some of the key decisions made—and lessons learned—in the years it has taken to build it.]]></description><link>https://stripe.dev/blog/how-we-built-it-stripe-radar</link><guid isPermaLink="true">https://stripe.dev/blog/how-we-built-it-stripe-radar</guid><dc:creator><![CDATA[Ryan Drapeau]]></dc:creator><pubDate>Wed, 29 Mar 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[How Stripe builds interactive docs with Markdoc]]></title><description><![CDATA[Markdoc delivers a good user experience without compromising the authoring experience. It enables writers to express interactivity and simple page logic without mixing code and content.]]></description><link>https://stripe.dev/blog/markdoc</link><guid isPermaLink="true">https://stripe.dev/blog/markdoc</guid><dc:creator><![CDATA[Ryan Paul]]></dc:creator><pubDate>Tue, 13 Sep 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[Migrating millions of lines of code to TypeScript]]></title><description><![CDATA[On Sunday, March 6, we migrated we converted more than 3.7 million lines of code with a single pull request. The next day, hundreds of engineers came in to start writing TypeScript for their projects.]]></description><link>https://stripe.dev/blog/migrating-to-typescript</link><guid isPermaLink="true">https://stripe.dev/blog/migrating-to-typescript</guid><dc:creator><![CDATA[Andrew Lunny]]></dc:creator><pubDate>Fri, 20 May 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[Fast builds, secure builds. Choose two]]></title><description><![CDATA[Stripe's continuous integration system is responsible for orchestrating build pipelines and executing tens of thousands of test suites. To meet our exceptionally high security bar, we lean on a combination of open-source tech and novel engineering.]]></description><link>https://stripe.dev/blog/fast-secure-builds-choose-two</link><guid isPermaLink="true">https://stripe.dev/blog/fast-secure-builds-choose-two</guid><dc:creator><![CDATA[Sushain Cherivirala]]></dc:creator><pubDate>Wed, 04 May 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[Sorbet: Stripe’s type checker for Ruby]]></title><description><![CDATA[Stripe uses Sorbet to type check our 15 million lines of Ruby code, making engineers more productive. Here's why we love Sorbet.]]></description><link>https://stripe.dev/blog/sorbet-stripes-type-checker-for-ruby</link><guid isPermaLink="true">https://stripe.dev/blog/sorbet-stripes-type-checker-for-ruby</guid><dc:creator><![CDATA[Jake Zimmerman]]></dc:creator><pubDate>Mon, 28 Mar 2022 00:00:00 GMT</pubDate></item><item><title><![CDATA[Stripe’s payments APIs: The first 10 years]]></title><description><![CDATA[Abstracting away the complexity of payments has driven the evolution of our APIs over the last decade. Learn more about Stripe payments APIs here.]]></description><link>https://stripe.dev/blog/payment-api-design</link><guid isPermaLink="true">https://stripe.dev/blog/payment-api-design</guid><dc:creator><![CDATA[Michelle Bu]]></dc:creator><pubDate>Tue, 15 Dec 2020 00:00:00 GMT</pubDate></item></channel></rss>