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.
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 event | Postal event | When | Properties |
|---|---|---|---|
checkout.session.completed | stripe.checkout.session.completed | Checkout completed | checkout_session_id, amount, currency, mode, payment_status, subscription_id |
checkout.session.async_payment_succeeded | stripe.checkout.session.async_payment_succeeded | Asynchronous payment (boleto, Pix) succeeded | same as checkout |
checkout.session.async_payment_failed | stripe.checkout.session.async_payment_failed | Asynchronous payment declined or expired | same as checkout |
customer.created | stripe.customer.created | Customer created | customer_id, name, phone |
customer.updated | stripe.customer.updated | Customer updated | same as customer.created |
customer.deleted | stripe.customer.deleted | Customer deleted (a deletion, see below) | same as customer.created |
customer.subscription.created | stripe.customer.subscription.created | Subscription created | subscription_id, status, plan, price_id, product_id, amount, currency, interval, trial_end, current_period_end |
customer.subscription.updated | stripe.customer.subscription.updated | Subscription changed (plan, status, scheduled cancellation) | same as the subscription, plus cancel_at_period_end |
customer.subscription.paused | stripe.customer.subscription.paused | Subscription paused | same as the subscription |
customer.subscription.resumed | stripe.customer.subscription.resumed | Subscription resumed | same as the subscription |
customer.subscription.trial_will_end | stripe.customer.subscription.trial_will_end | Free trial ends in 3 days | same as the subscription |
customer.subscription.deleted | stripe.customer.subscription.deleted | Subscription canceled | same as the subscription, plus cancellation_reason |
invoice.paid | stripe.invoice.paid | Invoice paid | invoice_id, invoice_number, amount, currency, status, billing_reason, plan, hosted_invoice_url, invoice_pdf |
invoice.payment_failed | stripe.invoice.payment_failed | Invoice payment failed | same as the invoice, plus attempt_count and next_payment_attempt |
invoice.upcoming | stripe.invoice.upcoming | Upcoming invoice (days before the charge) | same as the invoice, plus amount_due and period_end |
charge.dispute.created | stripe.charge.dispute.created | Dispute opened | dispute_id, charge_id, amount, currency, reason, status |
charge.dispute.closed | stripe.charge.dispute.closed | Dispute closed | same 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.
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:
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.