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
| Workflow | Endpoint | Owner |
|---|---|---|
| InBlack subscription and entitlement billing | POST https://api.portal.inblack.app/webhooks/stripe | Portal API |
| Stripe Connect accounting intake | POST https://api.inblack.app/webhooks/stripe | Accounting API |
| Storefront checkout completion | POST https://api.commerce.inblack.app/webhooks/stripe | Commerce 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-Signatureheader. - 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.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paidinvoice.payment_failedcustomer.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.
| Outcome | HTTP behavior |
|---|---|
| Invalid signature | 400 |
| Event has no connected account, or no tenant mapping | 200, ignored |
| Duplicate Stripe event ID | 200, no duplicate processing |
| Audit or inbox persistence failure | 503 |
| Accepted | 200, 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.