Skip to content

fix(webhook): paginate entitlements when has_more=true in summary event#197

Open
matingathani wants to merge 1 commit intostripe:mainfrom
matingathani:issue-118-entitlements
Open

fix(webhook): paginate entitlements when has_more=true in summary event#197
matingathani wants to merge 1 commit intostripe:mainfrom
matingathani:issue-118-entitlements

Conversation

@matingathani
Copy link
Copy Markdown

@matingathani matingathani commented Mar 30, 2026

Summary

entitlements.active_entitlement_summary.updated webhook events include at most 10 entitlements in the event body. When a customer has more than 10 active entitlements, the webhook body sets has_more: true but the handler only processed the embedded page, silently dropping the rest.

Root cause: handleEntitlementSummaryEvent() only mapped summary.entitlements.data directly, with no pagination path.

Fix: When summary.entitlements.has_more === true, fetch the complete list of active entitlements for the customer from the Stripe API (paginating with starting_after until has_more is false). Use fresh timestamp when fetched from API.

Test plan

  • Unit test: mock event with has_more: true and verify stripe.entitlements.activeEntitlements.list is called with customer filter
  • Unit test: mock two pages of entitlements and verify all are upserted
  • Unit test: event with has_more: false still uses webhook body data directly (no API call)
  • Existing entitlement tests pass

Closes #118

Copilot AI review requested due to automatic review settings March 30, 2026 00:53
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes entitlements.active_entitlement_summary.updated webhook handling so customers with >10 active entitlements are fully processed by paginating via the Stripe API when the webhook payload indicates has_more: true.

Changes:

  • Adds a pagination path that fetches all active entitlements for the event’s customer when the webhook payload is truncated (has_more: true).
  • Uses a “fresh” sync timestamp (current time) when entitlements were fetched from Stripe instead of relying on event.created.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +195 to +201
while (page.has_more) {
const lastId = page.data[page.data.length - 1].id
page = await this.deps.stripe.entitlements.activeEntitlements.list({
customer: customerId,
limit: 100,
starting_after: lastId,
} as Stripe.Entitlements.ActiveEntitlementListParams)
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pagination loop assumes page.data is non-empty when page.has_more is true. If Stripe ever returns an empty page with has_more: true (or a filtering edge case yields data.length === 0), page.data[page.data.length - 1].id will throw and webhook processing will fail. Add a guard before computing lastId (e.g., break/throw with a clear error when page.data.length === 0) and only set starting_after when a last item exists (similar to the existing manual pagination pattern in tests).

Copilot uses AI. Check for mistakes.
Comment on lines +187 to +205
if (summary.entitlements.has_more) {
// Webhook body is truncated — page through all active entitlements for this customer
entitlementItems = []
let page = await this.deps.stripe.entitlements.activeEntitlements.list({
customer: customerId,
limit: 100,
} as Stripe.Entitlements.ActiveEntitlementListParams)
entitlementItems.push(...(page.data as EntitlementItem[]))
while (page.has_more) {
const lastId = page.data[page.data.length - 1].id
page = await this.deps.stripe.entitlements.activeEntitlements.list({
customer: customerId,
limit: 100,
starting_after: lastId,
} as Stripe.Entitlements.ActiveEntitlementListParams)
entitlementItems.push(...(page.data as EntitlementItem[]))
}
fetched = true
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces a new code path that calls the Stripe API and manually paginates entitlements when summary.entitlements.has_more is true, but there are no unit tests covering it. Add tests to verify: (1) activeEntitlements.list is called when has_more: true, (2) multiple pages are fetched and all entitlements are upserted, and (3) has_more: false does not trigger API calls (uses webhook body).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Active Entitlements Summary Updated Webhook - Not Paging Beyond 10 Records in Webhook Body

2 participants