HTTP action

Stripe webhook to any URL with JSONata

Fire a signed, JSONata-templated HTTPS request to any endpoint when a Stripe event happens. The escape hatch for every integration we don't ship natively.

Free trial · From $19/mo · No credit card required

The problem

There's always one more service. Your internal admin API. A Slack-incompatible chat tool. A staging webhook. A friend's side project. You need a way to send a stripe webhook to any url with a templated body and proper retries, without standing up a relay server or paying Zapier per task. The HTTP action is that escape hatch. Configure a URL, headers, method, and a JSONata-templated JSON body referencing the Stripe event. Add an HMAC-signed signature header so the receiver can verify it's really from your workflow.

Outbound retries on 5xx, dedupes on Stripe's invocation id, surfaces 4xx errors immediately, and respects retry-after on 429. It's the action you reach for when none of the other six fit. JSONata is powerful enough that most ad-hoc transformations — currency conversion, conditional fields, derived booleans — happen inside the action config, not in glue code on the other end.

How stripe webhook to any url works

  1. 1

    Set URL, method, and headers

    Pick the HTTPS endpoint, the method (POST, PUT, PATCH), and any static headers (Authorization, Content-Type). Header values support JSONata if you need them dynamic — useful for per-customer auth tokens.
  2. 2

    Template the body with JSONata

    Write a JSON body referencing the Stripe event. JSONata gives you conditionals ($contains, $boolean), arithmetic, date formatting, and the ability to shape the payload to whatever the receiver expects — no transform service needed.
  3. 3

    Enable HMAC signing (recommended)

    Outbound can sign every request with an HMAC over the body using a shared secret. Receivers can verify the signature, dedupe on the X-Outbound-Invocation-Id header, and trust the source — same pattern as Stripe's own webhooks.
  4. 4

    Tune retries and timeouts

    Default: retry on 5xx with exponential backoff up to 24 hours, respect 429 retry-after, fail fast on 4xx. Override per-action if your receiver needs different semantics (e.g., one-shot fire-and-forget for non-critical sends).
trigger:
  event: customer.subscription.updated
steps:
  - action: outbound.http
    config:
      method: POST
      url: https://admin.acme.com/internal/subscription-changed
      headers:
        Authorization: "Bearer {{ secrets.ADMIN_TOKEN }}"
        Content-Type: application/json
      sign_with: hmac-sha256
      body:
        customer_id: "{{customer.id}}"
        new_plan:    "{{items.data[0].price.nickname}}"
        seats:       "{{items.data[0].quantity}}"
        renews_at:   "{{$fromMillis(current_period_end * 1000)}}"
        is_upgrade:  "{{items.data[0].price.unit_amount > previous_attributes.items.data[0].price.unit_amount}}"

Example workflow configuration

Screenshot of the HTTP action config in the Stripe Workflow builder. Shows a URL input, a method dropdown set to POST, a headers grid with one static header and one JSONata-templated header, a JSON body editor with templated values highlighted, an HMAC sign toggle enabled with a secret name reference, and a retries section with a max-retries field.

URL, headers, JSONata body, optional HMAC signature. The action you use when none of the six branded ones fit.

Outbound vs custom webhook handler

Outboundcustom webhook handler
Hosted relay to maintain
Signature verification + retries built-inyou build it
Idempotent on Stripe invocation idyou build it
Setup time≈4 minutesHalf a day to a week
JSONata body templating
Pricing modelFrom $19/mo flatHosting + dev time
Lives inside the Stripe Dashboard

Frequently asked questions

How is this different from Stripe's native webhook endpoints?+
Stripe's native endpoints send a fixed payload to a URL. The HTTP action lets you shape the payload per-workflow with JSONata, mix in derived fields, route to different URLs by event, and chain it with other workflow steps. It's a workflow step, not a global webhook.
Can I call non-HTTPS endpoints?+
No. HTTPS only, with TLS 1.2 or higher. We don't proxy through HTTP because we don't want signed payloads going over plaintext. If you need to hit something internal, expose it via a reverse proxy with TLS termination or use the Postgres action directly.
What's in the HMAC signature?+
An HMAC-SHA256 of the request body using a shared secret you configure in Outbound. We send it as the X-Outbound-Signature header, with the timestamp as X-Outbound-Timestamp. Same shape as Stripe's signature so you can reuse the verification code you already wrote.
How are 4xx errors handled?+
Fail fast. A 4xx means the receiver rejected the request and retrying won't help — the execution log records the status, response body, and stops. 5xx and 429 retry with exponential backoff. The retry policy is configurable per action if you want to retry 4xx for testing.
Is there a payload size limit?+
1 MB per request body. Stripe events themselves rarely exceed 100KB even with expansions, so the limit is mostly there to keep one bad workflow from accidentally posting megabytes of data into someone's API. If you legitimately need more, talk to support.