Webhook
๐ Overviewโ
internal/api/v1/billing/services/webhook.service.js handles Stripe webhook payloads for the billing domain. It normalises charge, invoice, customer, subscription, refund, dispute, and product events, keeps Mongo projections in sync, and notifies other services (CRM, Support, Queue Manager) about billing updates.
Primary Controller: webhook.controller.js
๐๏ธ Collections & Modelsโ
billing.charges,billing.subscriptions,billing.disputes,billing.refunds,billing.products,billing.customers.crm.contactsโ Updates or creates contacts based on Stripe customer data.support.*collections โ Opens conversations and logs payment activity.currencyโ Retrieves symbols for notification messages.
๐ External Dependenciesโ
- Stripe โ Expanded retrieves for charges, disputes, invoices, and subscriptions.
- Socket Emit Utility โ Sends real-time updates to Support UI (
emit-socket). - Logger Utility โ Records success/failure of downstream operations.
- withTransaction โ Ensures atomic creation of support conversations, rooms, and messages.
- Stripe Config Utility โ Hydrates Stripe client for connected accounts.
๐ Event Flowโ
flowchart TD
Stripe[Stripe Webhooks] --> Controller
Controller --> Service
Service --> Mongo[(MongoDB)]
Service --> CRM[CRM Contacts]
Service --> SupportSockets[Support Sockets]
Service --> QueueManager
Service --> Logger
๐ง Key Behavioursโ
- Upserts Stripe entities into Mongo models to maintain a searchable cache.
- Applies connected-account context (
stripe_user_id) to every persisted entity for multi-tenant separation. - Mirrors subscription metadata onto invoices and charges to maintain CRM attribution.
- Generates support-room system messages on successful invoice payments, even creating conversations when none exist.
- Uses
getMappingto translate Stripe customer fields into CRM contact schemas when mappings are configured.
๐งฉ Event Handlersโ
Chargesโ
newCharge({ chargeDetails, stripeBilling })- Retrieves expanded charge from Stripe (customer, subscription, balance transaction) and inserts into
billing.charges. - Handles duplicate key errors gracefully (ignored as idempotent replay).
- Retrieves expanded charge from Stripe (customer, subscription, balance transaction) and inserts into
updateCharge({ chargeDetails, stripeBilling })- Upserts existing charge documents with latest payload.
Invoicesโ
newInvoice({ invoice, stripeBilling })- Propagates invoice metadata to associated charge.
paidInvoice({ invoice, stripeBilling })- When
status = 'paid', locates or creates CRM contact and support conversation. - Emits socket events to join/create rooms and logs system messages with formatted payment amount.
- When
Disputes & Refundsโ
newDispute,updateDispute,closeDispute- Persist dispute lifecycle into
billing.disputes, expanding charge references for context.
- Persist dispute lifecycle into
createRefund,updateRefund- Track refunds in
billing.refunds, storing amount, currency, and status.
- Track refunds in
Customersโ
newCustomer- Inserts Stripe customer snapshot and updates or creates CRM contact based on mapping configuration. Supports auto-creation when
stripeBilling.new_contactflag is set.
- Inserts Stripe customer snapshot and updates or creates CRM contact based on mapping configuration. Supports auto-creation when
updateCustomer- Upserts customer document and re-applies mapping updates to CRM contact.
deleteCustomer- Marks customer as deleted and unsets
stripe_customer_idfrom linked contacts.
- Marks customer as deleted and unsets
Subscriptionsโ
createSubscription,updateSubscription,deleteSubscription- Persist subscription details, mirror metadata to latest invoice and charge, and upsert in
billing.subscriptions. deleteSubscriptionstores latest payload to track final status.
- Persist subscription details, mirror metadata to latest invoice and charge, and upsert in
Productsโ
createProduct,updateProduct,deleteProduct- Synchronise Stripe product catalogue into
billing.productsfor local caching.
- Synchronise Stripe product catalogue into
๐งช Testing Notesโ
- Simulate webhook payloads in unit tests to confirm idempotent upserts across duplicate deliveries.
- Validate support socket emissions with integration tests that stub
emit-socketto avoid side effects. - Ensure mapping logic is covered with fixtures that include
stripeBilling.mappings.
โ ๏ธ Operational Considerationsโ
- All handlers guard against duplicate Stripe deliveries by upserting on
stripe_id+stripe_user_id. - Long-running operations (support room creation) execute within
withTransaction; monitor Mongo transaction timeouts. - Stripe connection errors are surfaced via
customhelper for consistent error reporting upstream.