opsintermediate12 min setupby Mantle

Stripe payment failure alert

A failed Stripe payment reaches the billing channel and the billing inbox with the customer already looked up.

Payment failedRead the customePing #billingE-mail billing

Drag to pan, pinch to zoom. This is the real graph the template installs.

Purpose

A failed Stripe payment reaches the billing channel and the billing inbox with the customer already looked up.

How it works

What it does

  • Listens for Stripe's invoice.payment_failed.
  • Reads the customer so both messages carry a name and an address, not an opaque id.
  • Posts to Slack and e-mails the billing inbox.

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

  1. 01Payment failedtrigger.stripe_eventTrigger
  2. 02Read the customerstripe.get_customerApp
  3. 03Ping #billingslack.post_messageApp
  4. 04E-mail billingmail.sendDestination
View template JSON
{
  "format": "mantle-workflow/v1",
  "id": "stripe-failed-payment-alert",
  "name": "Stripe payment failure alert",
  "graph": {
    "nodes": [
      {
        "id": "event",
        "type": "trigger.stripe_event",
        "label": "Payment failed",
        "config": {
          "events": [
            "invoice.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": "slack",
        "type": "slack.post_message",
        "label": "Ping #billing",
        "config": {
          "channel": "#billing",
          "text": ":credit_card: Payment failed for *{{ steps.customer.name }}* ({{ steps.customer.email }}) — Stripe customer {{ steps.customer.id }}"
        },
        "worker_type": "generic-worker",
        "position": {
          "x": 700,
          "y": 220
        }
      },
      {
        "id": "email",
        "type": "mail.send",
        "label": "E-mail billing",
        "config": {
          "to": "billing@example.com",
          "subject": "Payment failed — {{ steps.customer.email }}",
          "body_text": "Stripe event {{ $json.type }}.\nCustomer {{ steps.customer.id }} ({{ steps.customer.email }}) is now delinquent: {{ steps.customer.delinquent }}.\n"
        },
        "worker_type": "generic-worker",
        "position": {
          "x": 1020,
          "y": 220
        }
      }
    ],
    "edges": [
      {
        "from": "event",
        "to": "customer"
      },
      {
        "from": "customer",
        "to": "slack"
      },
      {
        "from": "slack",
        "to": "email"
      }
    ]
  }
}

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.
  • 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).
  • SendGridAPI key

    Used by E-mail billing.

    Credential: {"api_key"} or {"username", "password"}

Setup

  • The Stripe key needs write access to webhook endpoints; MANTLE_APP_PUBLIC_BASE_URL must be an https URL Stripe can reach before publish.
  • Pick Slack and mail connections; set the channel and the recipient.
  • Add more events to events (customer.subscription.deleted, charge.dispute.created) to widen it.

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

E-mail billing mail.send

{
  "body": null,
  "message_id": "",
  "provider": "",
  "sent": true,
  "status_code": 0,
  "thread_id": ""
}

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.