Failed payment to a HubSpot ticket and a Slack alert
A Stripe payment failure opens a HubSpot ticket against the customer and pings the billing channel.
Drag to pan, pinch to zoom. This is the real graph the template installs.
Purpose
A Stripe payment failure opens a HubSpot ticket against the customer and pings the billing channel.
How it works
What it does
- Listens for Stripe's
invoice.payment_failedandpayment_intent.payment_failed. - Reads the Stripe customer so the ticket carries a name and an e-mail, not just an id.
- Opens a HubSpot ticket at high priority and posts the ticket id to Slack.
Validation
This template is contract-validated: the unit suite checks every node type, every config key and the whole graph against the same rules a saved workflow passes. It has not been run against the live provider — the ids, names and columns below are placeholders to replace with your own.
Nodes 4
- 01Payment failedtrigger.stripe_eventTrigger
- 02Read the customerstripe.get_customerApp
- 03Open a tickethubspot.create_ticketApp
- 04Ping billingslack.post_messageApp
View template JSON
{
"format": "mantle-workflow/v1",
"id": "hubspot-ticket-from-failed-payment",
"name": "Failed payment to a HubSpot ticket and a Slack alert",
"graph": {
"nodes": [
{
"id": "event",
"type": "trigger.stripe_event",
"label": "Payment failed",
"config": {
"events": [
"invoice.payment_failed",
"payment_intent.payment_failed"
]
},
"worker_type": "generic-worker",
"position": {
"x": 60,
"y": 220
}
},
{
"id": "customer",
"type": "stripe.get_customer",
"label": "Read the customer",
"config": {
"customer_id": "{{ $json.data_object.customer }}"
},
"worker_type": "generic-worker",
"position": {
"x": 380,
"y": 220
}
},
{
"id": "ticket",
"type": "hubspot.create_ticket",
"label": "Open a ticket",
"config": {
"subject": "Payment failed — {{ steps.customer.email }}",
"hs_pipeline_stage": "1",
"hs_ticket_priority": "HIGH",
"content": "Stripe event {{ $json.type }} for customer {{ steps.customer.id }} ({{ steps.customer.email }})."
},
"worker_type": "generic-worker",
"position": {
"x": 700,
"y": 220
}
},
{
"id": "alert",
"type": "slack.post_message",
"label": "Ping billing",
"config": {
"channel": "#billing",
"text": "Payment failed for {{ steps.customer.email }} — HubSpot ticket {{ steps.ticket.id }}"
},
"worker_type": "generic-worker",
"position": {
"x": 1020,
"y": 220
}
}
],
"edges": [
{
"from": "event",
"to": "customer"
},
{
"from": "customer",
"to": "ticket"
},
{
"from": "ticket",
"to": "alert"
}
]
}
}Requirements
The connections and permissions each step needs, from the node contracts in the catalog.
- StripeAPI key
Used by Payment failed, Read the customer.
Credential: the Stripe secret key (sk_...)
- The key needs write access to webhook endpoints — a restricted key without it can read charges and still fail to publish. Publishing also needs MANTLE_APP_PUBLIC_BASE_URL set to an https URL Stripe can reach; Stripe refuses a livemode endpoint that is not https.
- Publishing creates the endpoint in Stripe; unpublishing, disabling or archiving deletes it. A workflow that is off is off at Stripe too.
- Each delivery is verified against Stripe's signature over '{timestamp}.{body}' and refused outside a 5-minute window.
- Test-mode and live-mode keys are separate accounts at Stripe: a test key registers an endpoint that only ever receives livemode=false events.
- Stripe allows 16 webhook endpoints per account and this trigger creates one per published workflow, so a 17th publish is refused with Stripe's own message.
- HubSpotBearer token
Used by Open a ticket.
Credential: the HubSpot private-app token, or {"token": "…"}
- SlackAPI key
Used by Ping billing.
Credential: the bot token (xoxb-…), or {"token": "xoxb-…"}
- The bot must be a member of a private channel before it can post there (Slack error not_in_channel otherwise).
Setup
- The Stripe key needs write access to webhook endpoints, and
MANTLE_APP_PUBLIC_BASE_URLmust be an https URL Stripe can reach before the workflow will publish. hs_pipeline_stageis a stage id from your HubSpot ticket pipeline —1is only a placeholder.- Pick Stripe, HubSpot and Slack connections and set the channel.
Sample input and output
Synthetic examples: node-contract samples or placeholders, never customer data.
Input
What the Payment failed trigger receives, as its node contract samples it.
{
"api_version": "2024-06-20",
"created": 1717070400,
"created_at": "2024-05-30T12:00:00Z",
"data_object": {
"amount": 4200,
"currency": "usd",
"customer": "cus_QAbC1dEfGh2Ijk",
"id": "pi_3Ox2mB2eZvKYlo2C1kQ8sJ4d",
"object": "payment_intent",
"receipt_email": "buyer@example.com",
"status": "succeeded"
},
"id": "evt_1Ox2mB2eZvKYlo2CvQ0Tn9aE",
"livemode": false,
"object_id": "pi_3Ox2mB2eZvKYlo2C1kQ8sJ4d",
"object_type": "payment_intent",
"payload": {
"id": "evt_1Ox2mB2eZvKYlo2CvQ0Tn9aE",
"object": "event"
},
"previous_attributes": {},
"request_id": "req_9Kc0Qm3sT1uW2x",
"type": "payment_intent.succeeded"
}Output
Ping billing slack.post_message
{
"channel": "",
"ok": true,
"ts": ""
}Failure handling
What the graph does when a step fails, read from its retry settings, error branches and error policies.
- A step that fails, after any retries, fails the run. The failed step is kept with its input and error.
See it run, then make it yours.
Templates install as drafts. Edit any step, and Workflow Health re-checks the graph on every save.