You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
backend: fix Superwall webhook handler to match actual payload schema
Symptom: real Superwall webhooks land with no app_user_id and get
rejected with 'missing app_user_id' even though Superwall.shared.identify()
ran client-side and the user shows up correctly in Superwall's dashboard.
Root cause: the handler was written against an imagined/normalized
payload shape — snake_case top-level fields — that doesn't match what
Superwall actually sends. The real shape (per
https://superwall.com/docs/integrations/webhooks) is:
{
"object": "event",
"type": "initial_purchase",
"projectId": ..., "applicationId": ..., "timestamp": ...,
"data": {
"originalAppUserId": "<uid OR $SuperwallAlias:UUID>",
"originalTransactionId": "<durable sub id>",
"transactionId": "<per-charge id>",
"productId": "com.omi.app.lite_monthly",
"expirationAt": <ms since epoch>,
"store": "APP_STORE" | "PLAY_STORE" | "STRIPE",
...
}
}
Key differences:
- All purchase data lives under `data`, not at top level
- Field names are camelCase, not snake_case
- `originalAppUserId` is the durable user id (was looking for `app_user_id`)
- `originalTransactionId` is the across-renewals sub id (was `subscription_id`)
- `expirationAt` is in milliseconds (we store seconds — convert with //1000)
- `store` values are UPPERCASE (APP_STORE/PLAY_STORE/STRIPE) not lowercase
Handler updates:
- dispatch_event extracts `data` from outer envelope, passes to handlers
- Each handler reads from camelCase keys via two helpers:
_extract_sub_id reads originalTransactionId (falls back to transactionId)
_extract_period_end_seconds reads expirationAt and converts ms to s
- _detect_source matches uppercase `store` values
- dispatch_event rejects `$SuperwallAlias:<UUID>` uids — that prefix
means the SDK was configured but identify() wasn't called before the
purchase, so we have no omi user to reconcile to
This is the same root cause we saw in the user-facing log message
'event initial_purchase missing app_user_id' — handler was reading the
wrong key.
0 commit comments