Skip to content

better-auth: onUserUpdate sends email on every profile change, causing Polar API rejection #379

@yorickvandervis

Description

@yorickvandervis

Description

The onUserUpdate hook in packages/polar-betterauth/src/hooks/customer.ts fires on every user profile update and unconditionally sends email + name to customers.updateExternal, even when neither field changed.

When a user updates a non-Polar field (e.g. username, avatar, custom profile flags), the Polar API rejects the call with:

A customer with this email address already exists.

The error is caught and only logged (doesn't block the update), but it's noisy and unnecessary.

Reproduction

  1. Configure createCustomerOnSignUp: true
  2. Sign up a user (Polar customer is created)
  3. Update any non-email field on the user profile (e.g. username)
  4. Observe the error in logs:
ERROR [Better Auth]: Polar customer update failed. Error: API error occurred:
{"detail":[{"loc":["body","email"],"msg":"A customer with this email address already exists.","type":"value_error"}]}

Root cause

onUserUpdate (source):

await options.client.customers.updateExternal({
  externalId: user.id,
  customerUpdateExternalID: {
    email: user.email,  // always sent, even if unchanged
    name: user.name,
  },
});

Two issues:

  1. The hook sends email on every update regardless of what actually changed
  2. The Polar API (PATCH /v1/customers/external/{id}) appears to run a uniqueness check on email that matches the customer's own existing email, rejecting an idempotent update

Suggested fix

The user.update.after hook in better-auth receives both the updated user and context. Ideally the hook should:

  • Only send fields that actually changed to Polar, or
  • Skip the Polar API call entirely when neither email nor name changed

This is similar to the fix applied in #161 for onBeforeUserCreate, where a check-before-create guard was added.

Environment

  • @polar-sh/better-auth: latest
  • @polar-sh/sdk: latest
  • better-auth: latest
  • Polar environment: sandbox (also reproducible in production)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions