StripeWebhookEvent

Overview

services/api/src/models/StripeWebhookEvent.js defines a Mongoose model that stores Stripe webhook events for deduplication. The file treats each received Stripe event as a ledger entry, so the application can detect retries and avoid applying the same payment mutation twice.

This model fits into the payments webhook flow. In services/api/src/modules/payments/payments.webhooks.js, the handler checks whether an event.id already exists before processing, and it writes a StripeWebhookEvent record after successful handling. That links this model directly to webhook idempotency.

[!NOTE] The schema disables automatic Mongoose timestamps. The stored fields are only eventId, type, and receivedAt.

How it works

sequenceDiagram
    participant WH as payments webhooks
    participant M as StripeWebhookEvent
    participant DB as MongoDB

    WH->>M: exists eventId
    M->>DB: query by eventId
    DB-->>M: found or not found
    M-->>WH: duplicate state

    WH->>M: create eventId type receivedAt
    M->>DB: insert ledger record
    DB-->>M: saved or duplicate key error
    M-->>WH: create result

The model itself is small, but its role is important. The webhook handler uses it as an idempotency ledger, and the eventId field has a unique constraint. If the same Stripe event arrives again, the duplicate insert is rejected and the event can be skipped safely.

[!IMPORTANT] eventId is the deduplication key. It is marked required, unique, and trim.

Key components

{
    "title": "StripeWebhookEvent",
    "content": "Mongoose model that stores Stripe webhook event identifiers, event types, and reception time for deduplication.",
    "type": "class",
    "filePath": "services/api/src/models/StripeWebhookEvent.js",
    "badges": [
        "public"
    ]
}

Data & fields

[
    {
        "name": "eventId",
        "type": "string",
        "required": true,
        "description": "Unique Stripe event identifier. It is trimmed and indexed as unique."
    },
    {
        "name": "type",
        "type": "string",
        "required": true,
        "description": "Stripe event type, stored as a trimmed string."
    },
    {
        "name": "receivedAt",
        "type": "Date",
        "description": "Timestamp assigned when the document is created. It defaults to the current date."
    }
]

Schema options

[
    {
        "name": "timestamps",
        "type": "boolean",
        "description": "Disabled for this schema, so Mongoose does not add createdAt or updatedAt fields."
    }
]

Behaviors

  • const { Schema } = mongoose — extracts the Mongoose schema constructor used to define the document structure.
  • new Schema(...) — declares the StripeWebhookEventSchema with three fields and timestamps: false.
  • eventId — enforces uniqueness, required presence, and trimming.
  • type — stores the Stripe event type as a required trimmed string.
  • receivedAt — defaults to new Date() when the document is created.
  • mongoose.model("StripeWebhookEvent", StripeWebhookEventSchema) — registers and exports the model as StripeWebhookEvent.

Workflows

1. Receive webhook event | `payments.webhooks.js` receives a Stripe event and identifies its `event.id`.
2. Check for prior processing | The handler queries `StripeWebhookEvent` to detect whether the event already exists.
3. Process the event | If it is new, the webhook flow continues with payment confirmation or subscription sync.
4. Persist the ledger entry | The handler creates a `StripeWebhookEvent` record with `eventId` and `type`.
5. Skip duplicates | A repeated `eventId` is rejected by the unique index and treated as already processed.

Gotchas & edge cases

[!WARNING] Duplicate Stripe deliveries are expected behavior. The model exists to prevent the same event from mutating state twice.

[!CAUTION] The unique constraint lives on eventId. If the same event arrives again, Mongoose or MongoDB can reject the insert before any new state is written.

[!NOTE] The model does not store Mongoose-managed timestamps. Only receivedAt records the reception time.

services/api/src/modules/payments/payments.webhooks.js is the consumer that checks and stores Stripe webhook events. It uses StripeWebhookEvent.exists({ eventId: event.id }) for the duplicate check and StripeWebhookEvent.create({ eventId: event.id, type: event.type }) to persist the ledger entry.

services/api/src/modules/payments/providers/stripe.provider.js is the provider module that validates Stripe webhook signatures before the webhook handler reaches this model. That makes StripeWebhookEvent part of the broader Stripe payment and subscription flow.