Skip to content
AM

Stripe webhooks (inbound)

InBlack has separate Stripe webhook receivers for platform billing, connected-account accounting intake, and Commerce checkout. Configure Stripe with the endpoint owned by the workflow; the routes are not interchangeable.

Endpoint ownership

WorkflowEndpointOwner
InBlack subscription and entitlement billingPOST https://api.portal.inblack.app/webhooks/stripePortal API
Stripe Connect accounting intakePOST https://api.inblack.app/webhooks/stripeAccounting API
Storefront checkout completionPOST https://api.commerce.inblack.app/webhooks/stripeCommerce API

Accounting also retains /webhooks/stripe/billing, but it returns 410 Gone because platform billing is owned by Portal API.

Shared wire requirements

Each receiver:

  • Reads the raw request body, capped at 64 KiB.
  • Reads the Stripe-Signature header.
  • Verifies the body and signature with Stripe’s Go webhook library and that receiver’s configured secret.
  • Returns a non-success response for an invalid signature.

Use a distinct Stripe endpoint secret for each receiver unless deployment configuration explicitly shares one. The repository does not define one platform-wide signing secret.

Portal billing

Portal handles these event types:

  • checkout.session.completed
  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • invoice.paid
  • invoice.payment_failed
  • customer.subscription.trial_will_end

Unrecognized types are logged and acknowledged. Duplicate detection uses Redis and PostgreSQL paths. Invalid signatures return 400; an unconfigured receiver returns 503. Most handler errors are acknowledged with 200 to avoid provider retries, while a canonical event persistence failure returns 503.

Accounting intake

Accounting expects Stripe Connect events with an account value. It resolves that connected account to a tenant, appends canonical webhook audit data, and inserts an idempotent Stripe event record before acknowledging the request. Accepted events are processed asynchronously.

OutcomeHTTP behavior
Invalid signature400
Event has no connected account, or no tenant mapping200, ignored
Duplicate Stripe event ID200, no duplicate processing
Audit or inbox persistence failure503
Accepted200, then asynchronous projection

Accounting’s event mapper and active accounting rules determine whether a received Stripe object produces a payment record or journal activity. Do not assume a fixed debit/credit mapping from the Stripe event type alone.

Commerce checkout

Commerce acknowledges a verified event and processes it asynchronously. Its current checkout receiver handles checkout.session.completed; other verified event types are logged as unhandled. The completed session must be paid and include the expected cart, tenant, and brand metadata before Commerce creates the downstream order state.

Invalid signatures return 400. A verified event returns 200 before asynchronous processing, so operators must use application logs and Commerce state to diagnose a post-acknowledgement failure.

Idempotency and retries

The receivers have different durability behavior. Accounting persists an inbox-style event record before 200; Portal has Redis/PostgreSQL deduplication; Commerce’s checkout handler acknowledges before asynchronous processing. Do not apply one receiver’s replay guarantees to another.

Stripe retries depend on the status returned by the selected receiver. For manual replay, resend the event from the corresponding Stripe webhook endpoint after confirming that downstream processing is idempotent.