Sign in Create account
Integrations

Stripe

Stripe customers, subscriptions, invoices, checkout and disputes become contacts and events that start automations.

With the Stripe integration, every customer becomes a contact and every charge, subscription or dispute becomes an event that can start an automation: an email when a payment fails, a reminder when the free trial is about to end, a survey when someone cancels.

#Create the connection

In the dashboard, open Integrations › Your app and click Connect on the Stripe card. The connection has its own URL, which you paste into the provider, and it shows the step-by-step instructions on the same screen.

Connection URL (example)
https://api.postal.so/integrations/crm/01a1c2d3-e4f5-7a6b-8c9d-0e1f2a3b4c5d/webhook

In Stripe, open Developers › Webhooks, click Add destination, paste the URL and choose the events from the table below. Then open the destination, reveal the Signing secret (whsec_…) and paste it into the connection. The secret is stored encrypted and is not shown again.

How Postal verifies requests

Every request needs a Stripe-Signature header that is valid for the secret and at most 5 minutes old. Without it, the response is 401 and nothing changes.

App integrations count toward your plan limit (app_integrations). If they are not in your plan, the cards appear locked with an Upgrade button.

#Events

Every event also carries stripe_event_id, customer_id and livemode. Money amounts come in the currency unit (49.9 for R$ 49.90), with the currency in uppercase (BRL).

Stripe eventPostal eventWhenProperties
checkout.session.completedstripe.checkout.session.completedCheckout completedcheckout_session_id, amount, currency, mode, payment_status, subscription_id
checkout.session.async_payment_succeededstripe.checkout.session.async_payment_succeededAsynchronous payment (boleto, Pix) succeededsame as checkout
checkout.session.async_payment_failedstripe.checkout.session.async_payment_failedAsynchronous payment declined or expiredsame as checkout
customer.createdstripe.customer.createdCustomer createdcustomer_id, name, phone
customer.updatedstripe.customer.updatedCustomer updatedsame as customer.created
customer.deletedstripe.customer.deletedCustomer deleted (a deletion, see below)same as customer.created
customer.subscription.createdstripe.customer.subscription.createdSubscription createdsubscription_id, status, plan, price_id, product_id, amount, currency, interval, trial_end, current_period_end
customer.subscription.updatedstripe.customer.subscription.updatedSubscription changed (plan, status, scheduled cancellation)same as the subscription, plus cancel_at_period_end
customer.subscription.pausedstripe.customer.subscription.pausedSubscription pausedsame as the subscription
customer.subscription.resumedstripe.customer.subscription.resumedSubscription resumedsame as the subscription
customer.subscription.trial_will_endstripe.customer.subscription.trial_will_endFree trial ends in 3 dayssame as the subscription
customer.subscription.deletedstripe.customer.subscription.deletedSubscription canceledsame as the subscription, plus cancellation_reason
invoice.paidstripe.invoice.paidInvoice paidinvoice_id, invoice_number, amount, currency, status, billing_reason, plan, hosted_invoice_url, invoice_pdf
invoice.payment_failedstripe.invoice.payment_failedInvoice payment failedsame as the invoice, plus attempt_count and next_payment_attempt
invoice.upcomingstripe.invoice.upcomingUpcoming invoice (days before the charge)same as the invoice, plus amount_due and period_end
charge.dispute.createdstripe.charge.dispute.createdDispute openeddispute_id, charge_id, amount, currency, reason, status
charge.dispute.closedstripe.charge.dispute.closedDispute closedsame as the dispute

Other Stripe events are accepted and ignored.

#Which contact

The contact is the Stripe customer, identified by the email that comes in the event itself (the customer's, the invoice's customer_email or the checkout's customer_details.email). Postal does not call the Stripe API: when the event carries only the customer ID (as subscriptions do), the contact already linked to that ID by an earlier event is used. With no email and no linked contact, the event is skipped and shows up as “ignored” in the log.

The customer's name becomes the contact's first and last name, and metadata.user_id (or userId, external_id) becomes the external_id: store your app's user ID there when you create the customer in Stripe.

Creating the customer with your app's ID
await stripe.customers.create({
  email: '[email protected]',
  name: 'Jane Doe',
  metadata: { user_id: user.id },
})

For customer.deleted, the connection says what to do with the contact: only record the event (the default), unsubscribe the contact (it stays in the audience, but receives no marketing) or delete the contact (no event, since the contact is gone). A deletion never creates a contact that Postal did not have.

#Connection settings

  • Events: only the checked ones become a contact and an event; the others are ignored (the response is still 202).
  • Sync the name: updates the contact's first and last name with what the provider sends.
  • Add to segment and Subscribe to topic: a static segment and a topic for every contact that arrives.
  • When the user is deleted in the app: only record the event, unsubscribe the contact or delete it.

Everything the connection changes on a contact carries the source crm:<connection id> (last_change_source), and each delivery appears in the connection's log, with the result for each person. A delivery that the provider repeats (same ID) is not processed twice.

#Trigger automations

Each event is stored on the contact under the name stripe.invoice.payment_failed, with the properties from the table above. On the connection screen, the Create automation button next to each event opens a new automation with the Event received trigger already filled in. Through the automations endpoints, the trigger is {"type": "event", "event": "stripe.invoice.payment_failed"}.

Example: when a charge fails, send an email with a button to the invoice ({{event.hosted_invoice_url}}) and, if there is no stripe.invoice.paid after 3 days, a second notice. In the emails, the event properties are available as {{event.amount}} in the steps or as template variables ({{{amount}}}).

#Test it

With the Stripe CLI, trigger test events to your destination:

Terminal
stripe trigger customer.created
stripe trigger invoice.payment_failed

The contact appears under Audience and the event under Events. In Stripe, each delivery shows Postal's response (202 accepted, 401 invalid signature, 409 connection paused).

What is next

A one-click connection (a Stripe app, with no secret to copy) is planned. For now, you create the destination by hand in Stripe.