state_stores: local: namespace: hookway memory: {} # Per process; select Redis or PostgreSQL for shared protection.# TEMPLATE: accept a Stripe webhook and build the event envelope.## This file has two jobs. It is a complete, runnable config so that# `make validate-examples` proves the template still compiles against the# current node schema. It is also the source of a builder palette template:# the config UI extracts `in` through `env` (see the TEMPLATES manifest in# web/src/graph/templates.ts) and drops them onto the canvas. The `out`# publish exists so the file is a valid standalone config; it is not part of# the inserted template. Drop onto a new flow, then wire envelope to your# destination.## Requires STRIPE_WEBHOOK_SECRET (the signing secret from the Stripe# dashboard, often named whsec_…), WEBHOOK_URL and WEBHOOK_SECRET for the# wrapper publish, and HOOKWAY_API_KEY to keep /metrics off an open port.## Do not enable security.trusted_proxy_headers unless security.trusted_proxies# lists your load-balancer CIDRs — an empty list trusts any peer's# X-Forwarded-For and lets callers spoof rate-limit keys. Pin Stripe's# published webhook IPs with an ip_allowlist node after hops if you want a# network-layer check in addition to HMAC; do not copy stale ranges into git.## Stripe signs `t=<unix>,v1=<hex>` on Stripe-Signature over "<ts>.<body>".# Hookway's hmac_sha256_ts_dot scheme is that layout. timestamp_tolerance# should stay around 5m; Stripe retries, so replay_guard drops duplicates.# secret_ref refreshes the env secret and overlaps old/new during rotation.server: addr: ":8080" read_timeout: "5s" write_timeout: "10s" idle_timeout: "60s" shutdown_timeout: "10s" publish_timeout: "10s" max_body_bytes: 1048576 max_inflight_requests: 256 fanout_concurrency: 8metrics: enabled: true path: /metrics addr: ":9090"security: api_key: "${HOOKWAY_API_KEY}" forwarded_header_allowlist: - Content-Typedestinations: events: type: webhook webhook: url: "${WEBHOOK_URL}" delivery_mode: sync timeout: "10s" retry: max_attempts: 5 initial_backoff: "500ms" max_backoff: "30s" auth: type: hmac secret: "${WEBHOOK_SECRET}"flows: stripe_billing: nodes: - {id: in, type: http_trigger, description: "Stripe posts event deliveries here.", path: /webhooks/stripe, methods: [POST]} - {id: hops, type: loop_guard, description: "Rejects a request that already looped through Hookway.", max_hops: 3} - {id: rl, type: rate_limit, store: local, description: "Caps inbound Stripe retries per source IP in this process.", rpm: 60, burst: 20, key_by: source_ip} - {id: body, type: read_body, description: "Buffers the raw body so the signature can be verified.", max_bytes: 1048576} - id: auth type: auth description: "Verifies Stripe-Signature (timestamped HMAC-SHA256)." scheme: hmac_sha256_ts_dot secret_ref: {provider: env, name: STRIPE_WEBHOOK_SECRET, refresh: 15m, overlap: 5m} signature_header: Stripe-Signature timestamp_tolerance: "5m" - {id: replay, type: replay_guard, store: local, description: "Drops a delivery Stripe already succeeded within the TTL.", ttl: 24h, max_entries: 100000} - {id: env, type: envelope, description: "Decodes the JSON event (`type`, `data.object`, …).", body_format: json} - {id: out, type: publish, description: "Wrapper publish so this file is a complete config. Not part of the inserted template.", destination: events, target: stripe-events} edges: [in>hops, hops>rl, rl>body, body>auth, auth>replay, replay>env, env>out]