Stripe Webhook Trigger stripe-webhook
Receives and validates Stripe webhook events to start payment-related workflows. Uses HMAC-SHA256 with the Stripe webhook signing secret and enforces a 5-minute timestamp window to block replay attacks. Three endpoints are registered automatically: payment success, payment failure, and refund. Supports event type filtering, amount thresholds, and currency restrictions.
When to Use
- Post-payment fulfillment: Provision product access, generate a licence key, or send a purchase confirmation email immediately after a
charge.succeededevent, without polling the Stripe API. - Subscription lifecycle management: React to
payment_intent.succeededandpayment_intent.payment_failedevents to activate, suspend, or downgrade customer subscriptions in your system. - Failed payment retry workflows: On a failed charge event, trigger a dunning sequence — send a payment-failure email, schedule a retry after 48 hours, and alert account managers for high-value customers.
- Invoice reconciliation: Use
charge.succeededevents to auto-reconcile open invoices in your ERP, matching the Stripe customer ID and amount to internal records. - Refund notifications: When a
charge.refundedevent arrives, trigger a customer notification workflow and update inventory or licence revocation if applicable.
/webhooks/payment/success, /webhooks/payment/failure, and /webhooks/refund. Register all three in your Stripe Dashboard under Developers → Webhooks and point them at the appropriate BizFirstAI endpoint URLs for your deployment.
stripe-signature header is more than 5 minutes old. Ensure your BizFirstAI server clock is synchronised via NTP. Clock skew greater than 5 minutes will cause legitimate events to be rejected.
Configuration
Security
| Field | Required | Description |
|---|---|---|
webhookSecret |
Required | Stripe webhook endpoint signing secret in whsec_… format. Found in the Stripe Dashboard under Developers → Webhooks → [your endpoint] → Signing secret. Store in BizFirst Credentials Manager and reference as {{ $credentials.stripe-webhook-secret }}. Never store the raw value in node configuration. Falls back to Stripe:WebhookSecret in appsettings.json if omitted. |
enableIdempotencyCheck |
Optional | Default false. When true, uses the Stripe event ID (event_id) as a deduplication key to prevent the same event from starting two workflow runs if Stripe retries the delivery. |
includeFullEventObject |
Optional | Default false. When true, the complete raw Stripe event JSON is written into the output under _raw_event. Useful when downstream nodes need Stripe fields not extracted by default. Increases payload size. |
Event Filtering
| Field | Required | Description |
|---|---|---|
allowedEventTypes |
Optional | Comma-separated list of Stripe event types to process. Other event types are silently ignored. Valid values: charge.succeeded, charge.failed, charge.refunded, payment_intent.succeeded, payment_intent.payment_failed, charge.dispute.created, charge.dispute.closed. Supports wildcard suffix (e.g. charge.*). Leave empty to process all event types. |
minimumAmount |
Optional | Minimum payment amount in the smallest currency unit (cents for USD). Events with an amount below this threshold are ignored. Default 0 (no minimum). Example: 1000 = only process payments of $10.00 USD or more. |
maximumAmount |
Optional | Maximum payment amount in the smallest currency unit. Events above this threshold are ignored. Default 0 (no maximum). minimumAmount must be less than or equal to maximumAmount when both are set. |
allowedCurrencies |
Optional | Comma-separated ISO 4217 currency codes (case-insensitive). Events in other currencies are ignored. Example: USD,EUR,GBP. Leave empty to process all currencies. Each code must be exactly 3 letters. |
requireKnownCustomer |
Optional | Default false. When true, only processes events that include a non-null Stripe customer ID (customer_id). Useful in B2B scenarios where anonymous or guest checkout events should be excluded. |
Sample Configuration JSON
{
"webhookSecret": "{{ $credentials.stripe-webhook-secret }}",
"allowedEventTypes": "charge.succeeded,charge.refunded,payment_intent.payment_failed",
"minimumAmount": 100,
"allowedCurrencies": "USD,EUR",
"enableIdempotencyCheck": true,
"requireKnownCustomer": true
}
Validation Errors
| Error | Cause |
|---|---|
Unknown event type: <value> | An entry in allowedEventTypes is not in the valid set and does not end with .*. |
MinimumAmount cannot be negative | minimumAmount is set to a negative number. |
MaximumAmount cannot be negative | maximumAmount is set to a negative number. |
MinimumAmount cannot be greater than MaximumAmount | Both amounts are set and minimumAmount exceeds maximumAmount. |
Invalid currency code: <value> | An entry in allowedCurrencies is not a 3-letter ISO alphabetic code. |
Missing 'stripe-signature' header | The inbound request has no stripe-signature header. The request did not originate from Stripe or was tampered with. |
Invalid signature header format | The stripe-signature header does not match the expected t=timestamp,v1=signature format. |
Webhook timestamp outside acceptable window | The timestamp in the signature header is more than 5 minutes old. Possible replay attack or server clock skew. |
Invalid Stripe signature | The computed HMAC-SHA256 does not match the provided signature. Likely a misconfigured webhookSecret. |
Webhook secret not configured | webhookSecret is empty and no fallback exists under Stripe:WebhookSecret in appsettings.json. |
Output
Success Port
| Field | Type | Description |
|---|---|---|
event_id | string | Unique Stripe event identifier (e.g. evt_1234567890abcdef). Use as an idempotency key for downstream writes. |
event_type | string | Stripe event type string (e.g. charge.succeeded, charge.refunded). |
payment_intent | string | Stripe charge or payment intent ID from data.object.id (e.g. ch_… or pi_…). |
amount | number | Payment amount in the smallest currency unit (e.g. cents). $10.00 USD = 1000. |
currency | string | ISO 4217 currency code in uppercase (e.g. USD, EUR). |
customer_id | string | Stripe customer ID (e.g. cus_…). Absent when the payment was made without a customer object. |
status | string | Payment or charge status from the event object (e.g. succeeded, failed, refunded). |
description | string | Optional description field from the charge or payment intent object. |
metadata | object | Key-value metadata attached to the Stripe charge or payment intent. Only present when metadata is non-empty. |
timestamp | string | ISO 8601 UTC timestamp derived from the Stripe event's Unix created field. |
Error Port
Fires when signature verification fails, the timestamp window is exceeded, the event type is filtered out, or the request body cannot be parsed as valid JSON. The output contains errorCode and message fields. All verification failures are logged for security audit trail purposes.
Sample Output JSON — charge.succeeded
{
"event_id": "evt_1QaB2cD3eF4gH5iJ6kL7mN8",
"event_type": "charge.succeeded",
"payment_intent": "ch_3QaB2cRKJHMJBpXx1Y2Z3A4B",
"amount": 4999,
"currency": "USD",
"customer_id": "cus_QaB2cD3eF4gH5iJ",
"status": "succeeded",
"description": "Subscription — Pro Plan — May 2026",
"metadata": {
"order_id": "ORD-2026-05-1234",
"plan": "pro"
},
"timestamp": "2026-05-26T08:30:00Z",
"_webhook_method": "POST",
"_webhook_path": "/webhooks/payment/success",
"_webhook_received_at": "2026-05-26T08:30:00.812Z"
}
Expression Reference
| Expression | Value returned |
|---|---|
{{ $output.stripe.event_type }} | Stripe event type string. |
{{ $output.stripe.event_id }} | Unique event ID for idempotency checks. |
{{ $output.stripe.amount }} | Amount in smallest currency unit (cents). |
{{ $output.stripe.currency }} | ISO 4217 currency code in uppercase. |
{{ $output.stripe.customer_id }} | Stripe customer ID. |
{{ $output.stripe.status }} | Charge or payment status. |
{{ $output.stripe.payment_intent }} | Charge or payment intent ID. |
{{ $output.stripe.metadata.order_id }} | Custom metadata value by key. |
{{ $output.stripe.timestamp }} | ISO 8601 event timestamp. |
Node Policies & GuardRails
- Never skip signature verification. The node uses constant-time HMAC-SHA256 comparison of the raw request body using the signing secret. Do not configure a blank secret. Verification failure routes to the error port and is logged — it does not silently pass through.
- Store the signing secret in Credentials Manager. The webhook signing secret grants the ability to spoof Stripe events. Reference it as
{{ $credentials.stripe-webhook-secret }}. Rotate it immediately if it is ever exposed. - Use
event_idas an idempotency key for order fulfillment. Stripe retries failed webhook deliveries for up to 72 hours. SetenableIdempotencyCheck: trueand use{{ $output.stripe.event_id }}as the idempotency key in any downstream node that provisions access, charges a customer, or writes a fulfillment record. - Store raw events for replay capability. Before processing the event, write the raw Stripe event body to a durable store (database or S3). If your fulfillment workflow fails after processing has started, you can replay the event from the stored body without relying on Stripe's retry window.
- Set amount thresholds for high-value alert workflows. Use
minimumAmountto route large payments (e.g. over $1,000) to a separate workflow that includes fraud review or account manager notification steps. - Test with Stripe CLI before production. Use
stripe trigger charge.succeededvia the Stripe CLI to send a signed test event to your development BizFirstAI endpoint. Validate that the signature is accepted, the output fields are populated as expected, and the downstream workflow completes correctly before enabling the endpoint in production. - Monitor server clock synchronisation. The 5-minute timestamp window requires the BizFirstAI server to have accurate system time. Configure NTP synchronisation on all server instances. A clock drift of more than 5 minutes will cause all Stripe webhook events to be rejected as potential replay attacks.
Examples
Post-Payment Fulfillment — Provision Subscription Access
{
"webhookSecret": "{{ $credentials.stripe-webhook-secret }}",
"allowedEventTypes": "charge.succeeded",
"allowedCurrencies": "USD,EUR,GBP",
"minimumAmount": 1,
"enableIdempotencyCheck": true,
"requireKnownCustomer": true
}
Fires only for successful charges linked to a known Stripe customer. The downstream workflow uses {{ $output.stripe.customer_id }} to look up the internal account record, activates the subscribed plan, and sends a purchase confirmation email. The idempotency check prevents double-provisioning if Stripe retries the event.
Failed Payment Dunning — Retry and Notify
{
"webhookSecret": "{{ $credentials.stripe-webhook-secret }}",
"allowedEventTypes": "charge.failed,payment_intent.payment_failed",
"enableIdempotencyCheck": true,
"requireKnownCustomer": true
}
Triggers a dunning workflow whenever a charge fails. The workflow sends a payment failure email to the customer using {{ $output.stripe.customer_id }} and {{ $output.stripe.amount }} to personalise the message, then schedules an automatic retry after 48 hours using a Delay node. If a second attempt fails, an account manager notification is dispatched.
High-Value Payment Alert — Fraud Review Routing
{
"webhookSecret": "{{ $credentials.stripe-webhook-secret }}",
"allowedEventTypes": "charge.succeeded",
"minimumAmount": 100000,
"allowedCurrencies": "USD",
"enableIdempotencyCheck": true,
"includeFullEventObject": false
}
Only processes charges of $1,000 USD or more. Each qualifying payment routes to a fraud-review workflow that posts the payment details — {{ $output.stripe.amount }}, {{ $output.stripe.customer_id }}, and {{ $output.stripe.metadata }} — to an internal Slack channel and creates a review task in Jira. Normal payments below the threshold are handled by a separate workflow with a lower minimumAmount setting.