Skip to content
Blog

9 min read

Stripe data in Postgres: what can replace webhooks?

Stripe's real-time sync simplifies reporting and troubleshooting, but product access still requires clear rules for delays and responsibilities.

By Written with AI assistance from the source

Layers of smooth rocks beside a calm coast
Photo: Jeffrey Eisen on Unsplash

Stripe data often ends up in several parts of a product. The payment takes place in Stripe, the user is stored in Supabase, and product access is determined by code running through a service such as Vercel. To keep these parts in sync, many teams have built webhook functions that receive events from Stripe and update their own database.

This works, but it also means taking responsibility for failed deliveries, duplicates, incorrect ordering and reconciliation with Stripe. On 15 October 2026, Stripe announced real-time sync from Stripe Data Pipeline to Postgres. This feature can simplify much of this, particularly if you use Supabase or another Postgres-based service.

But a synchronised database and a webhook solve different problems. The distinction matters when payment status determines what a user can do in the product.

What Stripe has launched

Stripe Data Pipeline has previously been used mainly to move Stripe data to data warehouses and storage services for analysis. The new Postgres sync is designed for databases used directly by applications.

You choose which Stripe tables to synchronise. Stripe first fills the database with existing records, then continues to copy changes as customers, payments, invoices and subscriptions are updated. Stripe describes this as real time, but it should be understood as continuous synchronisation with a short and variable delay – not as both systems changing at exactly the same time.

The service is in public preview. The documentation warns that a row may temporarily show an earlier state and that some types of changes may take longer to appear. This affects how safely you can make immediate decisions based on the synchronised copy.

The sync covers resources in Stripe's public API that can be retrieved as lists. You can choose data for customers, payment attempts, charges, invoices, refunds, Checkout sessions and subscriptions, among other resources. Each table corresponds to a Stripe resource and also contains sync metadata and the complete resource as JSON.

For Supabase and Neon, the connection can be set up through authorisation in the Stripe Dashboard. For other Postgres services, you will normally need to create a separate database user, grant Stripe the necessary permissions and allow a secure network connection. Stripe creates and manages its own schema containing the tables you have selected.

That schema should be treated as managed by Stripe and read-only for your application. Build your own views and tables alongside it, but do not change Stripe's tables, columns or content.

What the sync can replace

The clearest benefit is that you no longer have to use webhooks simply to build a local copy of Stripe.

A common setup looks like this:

  • Stripe sends an event when a subscription changes.
  • A server function verifies the signature.
  • The function interprets the event and updates a separate table.
  • The code tries to handle duplicates and events arriving in an unexpected order.
  • A separate job occasionally compares the local table with Stripe.

Data Pipeline takes over much of this process. Stripe writes the current resource state to Postgres, checks synchronised records against the source data and tries to correct discrepancies. You do not have to reconstruct the final state yourself by interpreting a sequence of events.

It is particularly useful for the following needs:

  • Reporting. You can combine payment data with users, organisations and product data in SQL.
  • Customer support. Support views can show subscriptions, invoices, refunds and payment status without separate API calls to Stripe.
  • Troubleshooting. You can compare the user's internal account with the Stripe records belonging to the customer.
  • Account pages. The product can show the current plan, invoice history and upcoming changes from the database.
  • Reconciliation. You can find accounts that lack a customer link or have a product tier that does not match Stripe.

The sync also reduces the need for tables that try to act as simplified copies of Stripe. Instead, you can create SQL views that translate Stripe's data model into the product's terminology, such as billing_overview or current_plan.

This does not mean that the entire application should read Stripe's raw tables directly. A view gives you a more stable boundary where you can select relevant columns, name concepts clearly and hide payment details that should not be available to the client.

What the sync does not replace

A webhook is an event: something has happened, act now. Data Pipeline is a synchronised copy: this is what the resource looks like once the update has reached the database.

This distinction means that webhooks are still relevant when a specific change should start a process. Examples include sending a receipt, adding an order to a queue, warning about a failed payment or responding to a dispute.

Webhooks have their own problems. Stripe may try to deliver an event again, the same event may be received more than once, and different events do not have to be delivered in the order in which they were created. A webhook handler therefore needs to be idempotent – the same message should be processable again without repeating the same action.

Data Pipeline makes it easier to avoid this logic when the aim is simply to store the latest state. However, a table change does not automatically provide the same clear signal about why something changed or which business process should run.

Be particularly careful with the following:

  • Order processing. Do not wait blindly for a row to change if the order needs to start being processed immediately after a confirmed payment.
  • Messages. A current invoice row does not tell you whether a particular email has already been sent.
  • One-off actions. A database query can run several times and must not accidentally create the same delivery or credit more than once.
  • Time-critical flows. Real-time sync still has a delay, particularly during a preview period.
  • Events without a suitable table. Data Pipeline synchronises resources, not necessarily every signal that Stripe can send.

A reasonable hybrid setup is therefore to let Data Pipeline own the copy of Stripe data and retain webhooks for selected events that genuinely need to start an action. The webhook code then becomes smaller: verify the event, register a job and respond quickly. It does not also need to maintain a complete replica of Stripe.

Choose a clear source of truth

The term source of truth means the system responsible for determining what actually applies. Problems arise when both Stripe and your own table are considered to have the final say.

A suitable division of responsibilities might be:

QuestionSource of truth
Has the payment succeeded?Stripe
Which price tier is the subscription on?Stripe
Which user belongs to the Stripe customer?Your database
Which product features does the tier provide access to?Your product configuration
Can the account use the feature right now?An explicit access decision in the application

The final decision is more complicated than checking whether a subscription has the status active. You may need to consider trials, scheduled cancellations, unpaid invoices, manually granted access, internal accounts or a grace period after payment problems.

Do not implement product access as a condition scattered across several interfaces and server functions. Keep the rules together in a view, database function or server module. The result can include fields such as has_access, plan_key, access_reason and source_updated_at.

You also need to decide how delays should be handled. If a customer has just paid but the sync has not yet completed, you can:

  • retrieve the latest resource directly from Stripe in the confirmation flow
  • let a webhook grant provisional access and later reconcile it with Data Pipeline
  • show a waiting state and check again
  • use a clearly defined grace period before access is removed

Do not let client-side code in the browser make this decision. Product access should be checked on the server or in the database with the correct permissions. In Supabase, this also means that the Stripe-managed schema should not be exposed directly through the client API. Create restricted views or your own derived tables and apply row-level security where user data is shown.

The link between your user and stripe_customer_id needs to be stable and unique. An email address is unsuitable as the primary link because it can change and appear on several accounts. Store Stripe's identifier on the correct internal user or organisation, and check that the same Stripe customer is not accidentally linked to conflicting accounts.

How to introduce the sync without creating new gaps

Start by mapping what your current webhooks actually do. Separate the responsibilities into storing Stripe data, managing product access and performing one-off actions. This shows what Data Pipeline can take over and what needs to remain.

You can use this checklist:

  • Document which Stripe events you receive and why.
  • Mark which handlers only copy data to Postgres.
  • Keep handlers that start deliveries, messages or other one-off actions.
  • Select only the Stripe tables needed by the product and reporting.
  • Place the sync in a separate schema and use a separate database role.
  • Ensure that browser clients cannot read raw payment or customer data.
  • Build your own views for product concepts instead of spreading Stripe statuses throughout the code.
  • Store when the source data was last updated so that outdated decisions can be detected.
  • Decide what the user sees while a payment or upgrade is waiting to be synchronised.
  • Test upgrades, downgrades, cancellations, refunds and failed payments.
  • Run the old and new solutions in parallel until you can compare the results.
  • Monitor the health of the sync and accounts where access does not match Stripe.

Do not remove webhook endpoints as soon as the pipeline tables have been filled. A historical backfill shows that data can be copied, but not that all future states and failure paths work in your product. Also test what happens when the sync is delayed or temporarily unavailable.

The most important change is therefore not that webhooks disappear. It is that you can stop using them as a home-built data-copying engine. Let Stripe synchronise its state, let your product define the access rules, and use events only where an actual response is needed.

When payment data, access and user flows are connected, the next step is to check the entire journey from purchase to a working account – read more about building for paying customers.


Source: Stripe blog

Want to know what your build would take?

Three questions, then you get a clickable sketch for free.

← All articles