Playbook
The pricing change notifier.
Stripe price update fans out to CSMs, customers, HubSpot deals, and your docs. One source of truth. Five downstream actions. Each independently retried.
8 min read
The pain
Why pricing changes go sideways
You raise prices on the Pro plan from $99 to $129. The Stripe price object updates. CSMs find out two weeks later when a customer escalates a renewal quote. Your docs site still shows $99 for a month. HubSpot deal stages aren't updated, so sales forecasts the wrong amount.
The four common downstream gaps:
- CSMs don't know. Customer-facing teams find out from the customer.
- Customers find out at renewal. No proactive notice = trust erosion.
- HubSpot deals stale. Open opportunities still forecast at the old price.
- Public docs lag. The pricing page on the marketing site shows yesterday's number.
The architecture
What we're building
A single workflow triggered by Stripe's price.updated webhook. Fan out to five downstream actions. Each action is its own step so partial failure doesn't undo the rest.
workflow({
id: "pricing-change-notifier",
trigger: { event: "stripe.price.updated" },
steps: async ({ event, step }) => {
const price = event.data;
// 1. Notify CSMs immediately
await step.run("notify-csms", () =>
slack.send("#csms", `Price ${price.id} changed: ${formatChange(price)}`)
);
// 2. Find affected open deals in HubSpot
const deals = await step.run("find-affected-deals", () =>
hubspot.deals.search({
filterGroups: [{ filters: [
{ propertyName: "deal_stage", operator: "NEQ", value: "closedwon" },
{ propertyName: "stripe_product_id", operator: "EQ", value: price.product },
]}],
})
);
// 3. Update each deal — fan out concurrently with cap
for (const deal of deals) {
await step.run(`update-deal-${deal.id}`, () =>
hubspot.deals.update(deal.id, {
amount: recomputeDealAmount(deal, price),
notes: `Price updated to ${price.unit_amount / 100}. Old amount: ${deal.amount}.`,
})
);
}
// 4. Email affected customers (60-day notice for paid customers)
const customers = await step.run("find-active-subscribers", () =>
stripe.subscriptions.list({ price: price.id, status: "active" })
);
for (const sub of customers) {
await step.run(`email-${sub.customer}`, () =>
sendPriceChangeEmail(sub.customer, price)
);
}
// 5. Re-deploy docs site to pick up new price
await step.run("redeploy-docs", () =>
vercel.redeploys.create({ projectId: DOCS_PROJECT_ID })
);
// 6. Audit
await step.run("audit", () =>
db.priceChanges.insert({
priceId: price.id,
oldAmount: price.previous_attributes?.unit_amount,
newAmount: price.unit_amount,
affectedDeals: deals.length,
affectedCustomers: customers.length,
changedAt: new Date(),
})
);
},
});The customer email
Get this part right
Price-change emails set tone for the relationship. Three rules: give 60 days notice minimum, explain the why, offer a one-time lock-in.
async function sendPriceChangeEmail(customerId, price) {
const customer = await stripe.customers.retrieve(customerId);
const oldAmount = price.previous_attributes?.unit_amount ?? 0;
const pctChange = ((price.unit_amount - oldAmount) / oldAmount) * 100;
return resend.emails.send({
to: customer.email,
subject: `Pricing update for your ${customer.name} plan`,
react: PriceChangeEmail({
customerName: customer.name,
oldAmount: oldAmount / 100,
newAmount: price.unit_amount / 100,
effectiveDate: addDays(new Date(), 60),
lockInOption: pctChange > 0 ? "/lock-current-price" : null,
}),
headers: { "Idempotency-Key": `price-change-${price.id}-${customerId}` },
});
}Edge cases
What goes wrong, and how to handle it
Stripe sends the same webhook twice. The workflow is triggered with idempotencyKey: `price-${price.id}-${price.updated}`. Duplicate triggers collapse.
HubSpot rate-limits during the deal update fan-out. Each deal update is its own step. Failed steps retry with backoff. Successful ones don't re-run.
Vercel deploy fails. The next manual deploy or push fixes it. The audit row records the deploy attempt so you know to check.
Wrong price was published. Roll back: update Stripe price to the old amount. The same workflow fires again with the corrected price. The audit table tracks both changes.
The math
What this costs on Rotor
A SaaS with ~50 active subscribers and ~30 open deals on a changed price. One price change = ~90 step-runs (notify + 30 deals + 50 customers + audit + redeploy).
Even with 10 price changes a year, that's under 1,000 step-runs. Fits Hobby ($9/mo) easily. The value is in the reliability, not the volume.
Fork this playbook on Rotor.
$9 to start. 30-day money back. Hard caps protect you from runaway bills.
Start shipping