How to Process Inbound Stripe Webhooks with Signature Verification and Routing
Receive Stripe event webhooks on an HMAC-verified Webhook trigger, branch on the event type with a Condition node, enrich the customer with the Stripe connector, and route a structured summary to a Slack channel.
What This Integration Does
Stripe sends a webhook every time something happens in your account: an invoice is paid, a payment fails, a subscription changes, a customer is created. Most of those events deserve a different reaction. This Spojit workflow listens for those webhooks on a single URL, confirms each request genuinely came from Stripe, decides what kind of event it is, looks up the customer behind it, and posts a clean, human-readable summary to the right Slack channel. The result is that your finance and support teams see the events they care about in Slack within seconds, with the customer name and amount already filled in, instead of digging through the Stripe dashboard.
The workflow is triggered by an external HTTP POST from Stripe to the workflow's Webhook URL. Each request is verified by HMAC before any step runs, so unsigned or tampered payloads are rejected. The parsed event body flows into a Condition node that branches on {{ input.type }}; the matching branch calls the Stripe connector to enrich the customer, then sends a message to Slack. The workflow holds no state between runs: every webhook is an independent, short-lived execution that returns a 202 with an executionId to Stripe immediately. Stripe retries delivery on its own schedule if it does not get a success, so the steps below are written to be safe to re-run.
Prerequisites
- A Stripe connection in Spojit (an API key connection) with read access to customers. See the Stripe connector docs.
- A Slack connection authorized to post to the channels you want to route to, and the channel names or IDs you intend to use. See the Slack connector docs.
- A signing secret from Stripe so Spojit can verify the HMAC signature on each incoming webhook. You will paste this into a signing connection.
- Access to the Stripe Dashboard to add a webhook endpoint and choose which event types to send.
Step 1: Add a Webhook trigger and verify the Stripe signature
Create a new workflow and set its Trigger type to Webhook. Spojit gives the workflow a unique URL that you will register in Stripe. To reject forged requests, attach a signing connection to the trigger. Stripe webhooks use a custom HMAC scheme, so choose the Custom signing scheme and paste your Stripe webhook signing secret into the connection. With a signing connection attached, Spojit computes the HMAC of each incoming body and compares it to the signature header before any node runs; requests that do not match are discarded and never start an execution.
The trigger output is the parsed JSON event from Stripe, available as {{ input }}. The two fields you will use most are {{ input.type }} (for example invoice.payment_failed) and the event object under {{ input.data.object }}. A trimmed example of what arrives:
{
"id": "evt_1Nv...",
"type": "invoice.payment_failed",
"data": {
"object": {
"customer": "cus_Qf3...",
"amount_due": 4900,
"currency": "usd"
}
}
}
For more on signing and the 202 response model, see Setting Up a Webhook Trigger.
Step 2: Tell Stripe where to send events
Copy the Webhook URL from the trigger. In the Stripe Dashboard, add a new webhook endpoint pointing at that URL, then select the event types you want Spojit to handle, such as invoice.payment_succeeded, invoice.payment_failed, and customer.subscription.deleted. Stripe shows the signing secret for this endpoint after you create it; that is the value that belongs in the Custom signing connection from Step 1. Send a test event from Stripe to confirm the endpoint reports a delivered, successful response.
Step 3: Branch on the event type with a Condition node
Add a Condition node after the trigger to route each event down the right path. Configure it to test {{ input.type }}. A common pattern is to send the True branch when the event equals invoice.payment_failed (an urgent, dunning-style alert) and let everything else fall to the False branch for a general notification. If you need more than two paths, chain a second Condition node on the False branch (for example, splitting invoice.payment_succeeded from subscription events). Keep the comparison exact, since Stripe event names are case-sensitive and dot-delimited.
For branching mechanics and operators, see Using Condition Nodes.
Step 4: Enrich the customer with the Stripe connector
On each branch, add a Connector node in Direct mode using the Stripe connector and the get-customer tool. Direct mode is the right choice here: it is one deterministic lookup with no AI cost. Map the tool's id input to the customer reference on the event, {{ input.data.object.customer }}, and set the node's output variable (for example customer). The tool returns the full customer record, so you can reference {{ customer.name }} and {{ customer.email }} in the message you build next. Stripe stores amounts in the smallest currency unit, so divide by 100 in your message text when you display {{ input.data.object.amount_due }}.
If you would rather have the intelligent layer assemble a one-line summary across several fields, you can instead use a Connector node in Agent mode with a Response Schema to force structured JSON; for a single predictable lookup, Direct mode is simpler and cheaper. See Using Connector Nodes in Direct Mode.
Step 5: Route a structured summary to Slack
At the end of each branch, add a Connector node in Direct mode using the Slack connector and the send-message tool. Set the channel input per branch so failed payments land in a finance channel and other events land in a general channel. Build the text input from the variables you collected:
:rotating_light: Payment failed for {{ customer.name }} ({{ customer.email }})
Amount due: {{ input.data.object.amount_due }} {{ input.data.object.currency }}
Event: {{ input.type }} ({{ input.id }})
If you only have a customer email and not a channel-ready user, the Slack lookup-user-by-email tool resolves an email to a Slack user so you can mention or direct-message them. For a deeper walk-through of Slack routing patterns, see How to Set Up Multi-Channel Notifications.
Step 6: Scaffold and refine with Miraxa
Rather than placing every node by hand, you can describe the shape to Miraxa, the intelligent layer across your automation, from the chat surface in the Workflow Designer. Try a specific prompt such as: "Add a Condition node that checks if {{ input.type }} equals invoice.payment_failed, connect the true branch to a Stripe get-customer Direct mode node mapped from {{ input.data.object.customer }}, then a Slack send-message node to the finance channel." Miraxa knows the workflow you are editing and will add and connect the nodes for you, asking first if anything is ambiguous. Once scaffolded, open each node's properties panel to fine-tune channel names, message text, and the output variable names.
Tips
- Stripe sends amounts in the smallest unit (cents). Divide by 100 in your message text, and always show
{{ input.data.object.currency }}so a multi-currency account is not misread. - Keep one Stripe webhook endpoint per workflow URL. If you need separate handling for very different event families, register a second endpoint and a second workflow instead of overloading one Condition chain.
- Use exact channel IDs in the Slack
send-messagenode rather than display names where you can; renamed channels keep the same ID and will not silently break routing. - Reference
{{ input.id }}(the Stripe event id) in your Slack message so anyone can trace a notification back to the source event in the Stripe Dashboard.
Common Pitfalls
- No signing connection. Without an attached signing connection, the Webhook trigger accepts any POST. Always configure the Custom scheme with your Stripe signing secret so forged events cannot start a run.
- Wrong signing secret. Stripe issues a different signing secret per endpoint. If you copy the secret from a different endpoint, every request fails verification and no executions appear. Re-copy the secret for the exact endpoint pointing at your Spojit URL.
- Treating retries as duplicates. Stripe retries delivery until it gets a success, so the same event can arrive more than once. Keep your steps idempotent (a read-only
get-customerplus a Slack post is safe), or enable opt-in deduplication on the trigger using the event-id header so repeats do not double-post. - Reading the wrong object. The customer reference and amount live under
{{ input.data.object }}, not at the top level. Pointingget-customerat{{ input.customer }}returns nothing.
Testing
Validate on a small scope before relying on it. From the Stripe Dashboard, send a single test event of one type (for example invoice.payment_failed) to your endpoint. Open the workflow's execution history in Spojit and confirm the run started, the signature passed, the Condition node took the expected branch, get-customer returned a customer, and the Slack message arrived in the intended channel with the name and amount filled in. Temporarily point the Slack node at a private test channel while you tune the message text, then switch to the real channels once the routing looks right. If a run does not appear at all, the signature check is the first thing to verify.