PlayerPal Authentication and Identity Architecture
Status: Working specification
Audience: Product, architecture, backend, frontend, data, security and implementation agents
Purpose: Define how PlayerPal authenticates players, administrators and integrations while maintaining a privacy-preserving loyalty identity graph without storing player email addresses.
1. Executive Summary
PlayerPal is a privacy-preserving loyalty and analytics platform connecting players, operators, game providers, affiliates and reward partners. PlayerPal maintains a persistent player account, loyalty ledger, campaign progress, rewards history and analytics, but does not store a player's email address, name or telephone number.
Players may register directly with PlayerPal, from an operator experience, or from a game-provider experience when the provider is the integration entry point. Player authentication can use passkeys, supported social identity providers, a generated PlayerPal ID with a password, recovery codes and additional MFA methods. The PlayerPal ID is issued during account creation and can therefore authenticate later visits; it is not a pre-existing initial registration credential. A successful authentication resolves to one private PlayerPal player identity. The player can see their own pp_plr_... ID, but operators and providers never receive it.
When a player connects an operator, PlayerPal creates an operator-scoped player alias such as pp_plr_opr_.... When activity is exchanged with a game provider, PlayerPal creates a provider–operator-scoped alias such as pp_plr_prv_.... All aliases privately resolve to the same player but are unlinkable by external parties.
A standards-compliant IAM platform such as Keycloak or ZITADEL handles authentication protocols, credentials, social federation, passkeys, MFA, sessions, OIDC/SAML and machine OAuth. PlayerPal remains authoritative for the player identity graph, pairwise aliases, consent, operator/provider relationships, loyalty ownership, analytics and business authorization.
2. Product Context
2.1 What PlayerPal provides
PlayerPal provides:
- Player registration and authentication without requiring email.
- A private, portable PlayerPal loyalty identity.
- Operator and provider integrations using audience-specific identifiers.
- Loyalty points, balances, tiers, campaigns and rewards.
- Player-facing loyalty information.
- Operator/provider-facing authorized player information.
- Engagement, retention, churn, cohort and product-affinity analytics.
- Aggregated market intelligence where contractually and legally permitted.
- Cross-operator portability only when the player has explicitly and securely linked the relationships.
2.2 Privacy position
PlayerPal must not persist:
- Player email addresses.
- Player names.
- Player telephone numbers.
- Operator credentials or session cookies.
- Unnecessary social-provider profile claims.
PlayerPal necessarily processes pseudonymous identifiers and gameplay data. These may remain personal data under applicable privacy law even though they are not direct identifiers. They must therefore be protected, access-controlled, retained appropriately and included in privacy-impact analysis.
2.3 Core design principle
ONE PRIVATE PLAYER IDENTITY
MANY AUDIENCE-SPECIFIC EXTERNAL ALIASES
NO PLAYER EMAIL STORED BY PLAYERPAL
3. Terminology
| Term | Meaning |
|---|---|
| Authentication | Proving control of a credential or trusted account. |
| Principal | Authenticated human or machine identity. |
| Player | Persistent PlayerPal loyalty and analytics entity. |
| Authentication identity | Credential identity such as a passkey or social iss + sub. |
| Operator assertion | Short-lived signed proof that an operator has authenticated a particular local player. |
| Canonical player | Private PlayerPal player entity to which loyalty and analytics attach. |
| Pairwise alias | Random external player ID usable only by a specific audience. |
| Audience | Operator or configured provider–operator integration allowed to use an alias. |
| Integration | Configured trust relationship and credential boundary represented by pp_int_.... |
| Identity linking | Audited act of connecting an authentication/source identity to a PlayerPal player. |
| Identity namespace | Trusted scope in which an external subject is unique. |
| Authorization | Deciding what an authenticated principal may do or access. |
4. Identifier Model
4.1 Entity identifiers
| Identifier | Represents | Visibility |
|---|---|---|
pp_plr_... | Player's private, portable PlayerPal identity | Player and PlayerPal only |
pp_plr_opr_... | Player alias issued to one operator | That operator and PlayerPal |
pp_plr_prv_... | Player alias issued to one provider–operator audience | That integration and PlayerPal |
pp_opr_... | Operator entity | Authorized parties |
pp_prv_... | Provider entity | Authorized parties |
pp_aff_... | Affiliate entity | Authorized parties |
pp_int_... | Configured integration/audience | Authorized parties |
pp_ses_... | PlayerPal session | Relevant client and PlayerPal |
4.2 Internal identifiers
Database primary keys should use UUIDv7. The formatted external IDs are separate from database keys.
internal player UUIDv7:
0198f2a4-8d7e-7c31-a8b2-4d6f91c7e201
player-visible PlayerPal ID:
pp_plr_7D4P6...
The internal UUIDv7 must never be exposed externally.
4.3 Pairwise aliases
Pairwise aliases should be generated from cryptographically random 128-bit values and encoded using Crockford Base32. They must not be reversible encodings of the private player UUID and must not be derived from PII or partner identifiers.
Recommended uniqueness:
UNIQUE(audience_id, external_alias)
UNIQUE(player_id, audience_id) WHERE revoked_at IS NULL
Example:
private player: pp_plr_7D4P6...
Operator A: pp_plr_opr_A91K7...
Operator B: pp_plr_opr_B38M2...
Provider A × Operator A: pp_plr_prv_X72Q4...
Provider A × Operator B: pp_plr_prv_M93R8...
5. Responsibility Boundaries
5.1 IAM platform responsibilities
Keycloak or ZITADEL should handle:
- OIDC and OAuth 2.0 protocol implementation.
- Social identity-provider federation.
- Passkey/WebAuthn credential registration and authentication.
- Optional username/password authentication.
- MFA and step-up authentication.
- Human login sessions and logout.
- Authorization Code flow with PKCE.
- Enterprise SAML/OIDC for administrators.
- OAuth client credentials for machine clients.
- Signing keys, token issuance, rotation and revocation.
- Authentication events and brute-force protection.
5.2 PlayerPal Identity Service responsibilities
PlayerPal must handle:
- Creation and lifecycle of private
pp_plr_...players. - Mapping IAM
iss + subidentities to players. - Mapping social identities without persisting email/profile claims.
- Pairwise operator and provider player aliases.
- Operator assertions and player consent.
- Identity link, unlink, merge and split operations.
- Alias rotation and revocation.
- Identity assurance levels.
- Audit history for sensitive identity operations.
5.3 PlayerPal Authorization Service responsibilities
PlayerPal must authorize:
- Organization and partner access.
- Operator/provider integration scopes.
- Player-alias use by audience.
- Report and analytics visibility.
- Campaign, reward and loyalty administration.
- Support access and impersonation.
- Cross-operator data access.
5.4 Product services
Product services own:
- Loyalty ledger and balances.
- Tier calculation.
- Campaign eligibility and progress.
- Rewards and redemption.
- Gameplay/event history.
- Segmentation and analytics.
6. Why IAM Is Used Broadly but Not as the Whole Product
PlayerPal should use a mature IAM platform for authentication rather than implementing passwords, social OIDC, WebAuthn, MFA and token issuance from scratch. However, the IAM user record is not the PlayerPal player.
The IAM subject answers:
Which authentication account successfully logged in?
The PlayerPal player answers:
Which loyalty identity owns this activity, balance and relationship graph?
Keeping these separate provides:
- IAM vendor portability.
- No IAM lookup on each gameplay event.
- Product-controlled pairwise aliases.
- Audited link/unlink/merge operations.
- Correct provider–operator scoping.
- Independence between authentication and loyalty data models.
- Business authorization more expressive than IAM roles.
Do not store loyalty balances, provider aliases, operator connections or gameplay facts as IAM attributes, groups or roles.
7. Security Domains
The deployment should separate four security populations:
IAM platform
├── Player authentication
│ ├── Social login
│ ├── Passkeys
│ ├── PlayerPal ID/password
│ ├── MFA
│ └── Recovery
├── Partner administration
│ ├── Operator administrators
│ ├── Provider administrators
│ └── Affiliate administrators
├── PlayerPal workforce
│ ├── Employees
│ ├── Support
│ └── Platform administrators
└── Machine identities
├── Operator clients
├── Provider clients
└── Batch integrations
Isolation may be implemented using separate Keycloak realms or separate ZITADEL projects/organizations, subject to a proof of concept. At minimum, clients, scopes, signing/audience policies and administrative permissions must be separated.
8. Supported Player Authentication Methods
| Method | Email required | Intended use | Status |
|---|---|---|---|
| Passkey/WebAuthn | No | Preferred registration and login | Required |
| Google/social OIDC using opaque subject | No, if provider/configuration permits | Convenient login | Required, provider-specific validation |
| Apple OIDC using opaque subject | No, if claim filtering is valid | Convenient privacy-oriented login | Recommended |
| Microsoft OIDC using opaque subject | No, if configured without email scopes | Convenient login | Recommended |
| PlayerPal ID + password | No | Fallback login | Optional |
| Hardware security key | No | Passkey/MFA | Recommended |
| TOTP authenticator | No | MFA | Optional |
| Recovery codes | No | Account recovery | Required |
| Email magic link | Yes | Not compatible | Excluded |
| SMS OTP | Phone required | Not compatible with no-PII model | Excluded |
8.1 Social login claim policy
PlayerPal needs only:
{
"iss": "https://social-idp.example",
"sub": "opaque-stable-subject"
}
Player-facing OIDC clients should request only:
openid
Do not request by default:
email profile phone
Each provider must be tested because provider behavior differs. The IAM configuration must not persist or emit email/profile claims for players. Automatic account linking by matching email must be disabled.
If a provider releases an email regardless of requested scopes, product/legal/security must decide whether transient receipt followed by immediate filtering complies with the requirement. If PlayerPal infrastructure may never receive email, that provider/configuration must not be enabled.
9. Authentication Flows
9.1 Flow A — Direct registration with passkey
Steps:
- The player starts registration.
- IAM creates a WebAuthn challenge.
- The player's authenticator creates a private/public key pair.
- The private key stays with the authenticator.
- IAM registers the credential and returns an immutable IAM subject.
- PlayerPal creates a private UUIDv7 player and
pp_plr_...display ID. - PlayerPal maps
IAM issuer + IAM subjectto the player. - PlayerPal creates an application session.
9.2 Flow B — Direct registration/login using social identity
Rules:
- Use Authorization Code flow with PKCE.
- Validate issuer, audience, nonce, state, signature and expiry.
- Store only the authentication identity necessary for subsequent login.
- Do not persist social access tokens unless a separate approved feature requires them.
- Never link accounts using matching email.
9.3 Flow C — Add another authentication method
An authenticated player may add a passkey or another social identity:
- Player logs into the existing PlayerPal account.
- Player initiates Add sign-in method.
- Require recent authentication or step-up authentication.
- Complete the new provider/passkey flow.
- Link the new
iss + subor credential to the currently authenticated player. - Write an immutable security audit event.
Never link an unauthenticated social identity to an account solely because claims appear similar.
9.4 Flow D — Register from an operator
The operator assertion proves the operator-local player. The IAM authentication proves control of a PlayerPal login identity. Player approval authorizes connecting the two.
9.5 Flow E — Connect an existing PlayerPal account to an operator
- Operator authenticates the player locally.
- Operator creates a short-lived, single-use signed assertion.
- PlayerPal opens its first-party popup.
- Player logs into PlayerPal using any supported method.
- PlayerPal shows the operator and requested connection.
- Player explicitly approves.
- PlayerPal creates
pp_plr_opr_...for that operator. - PlayerPal returns a one-time result code to the operator.
- Operator exchanges the code server-to-server and stores the alias.
The private pp_plr_..., IAM subject and social-provider subject are never returned to the operator.
9.6 Flow F — Connect a second operator
The same flow runs for Operator B. PlayerPal resolves the PlayerPal login to the existing private player and creates a new unrelated alias:
Operator A: pp_plr_opr_A91K7...
Operator B: pp_plr_opr_B38M2...
Neither operator can correlate the player through PlayerPal IDs.
9.7 Flow G — Administrator login
IAM establishes the administrator's identity. PlayerPal determines organization membership and business permissions.
9.8 Flow H — Operator/provider machine authentication
Recommended:
OAuth 2.0 Client Credentials
+ private_key_jwt
+ optional mTLS
- Partner signs a client assertion with its private key.
- IAM validates the registered public key.
- IAM issues a short-lived audience-restricted access token.
- The API validates token signature, issuer, audience, expiry and scopes.
- PlayerPal loads the integration and applies business authorization.
10. Operator Assertion Specification
10.1 Required claims
{
"iss": "pp_opr_01K_OPERATOR_A",
"sub": "opaque-operator-player-subject",
"aud": "https://identity.playerpal.example",
"jti": "unique-assertion-id",
"nonce": "random-one-time-value",
"iat": 1788428400,
"nbf": 1788428400,
"exp": 1788428700
}
10.2 Validation
- Resolve operator from authenticated/configured issuer.
- Validate signature against registered and active keys.
- Require exact PlayerPal audience.
- Allow maximum lifetime of approximately five minutes.
- Reject replayed
jtiand nonce. - Do not accept arbitrary operator scope from untrusted claims.
- Store assertion subject encrypted or as an issuer-keyed lookup value when persistence is necessary.
- Reject prohibited claims such as email, name and telephone number.
10.3 Browser transport
Do not place the assertion or access token directly in the URL. Use a short-lived opaque one-time code and server-side state. Return results to embedded operator experiences using validated postMessage origins.
11. Provider Alias Provisioning
Before a provider can report activity, it needs a provider–operator-scoped player alias.
Recommended flow:
- Player connects PlayerPal to an operator.
- PlayerPal creates the operator alias.
- For enabled provider integrations, PlayerPal creates or lazily provisions a provider–operator alias.
- Operator retrieves the provider alias server-to-server.
- Operator places that alias in the provider's game/session launch payload.
- Provider stores the alias with reportable activity.
- Provider later submits the alias in activity batches.
Example:
{
"sessionToken": "operator-owned-game-session-token",
"playerPalId": "pp_plr_prv_X72Q4..."
}
Normal game launch should not require a synchronous PlayerPal request if aliases can be provisioned/cached during enrollment or integration setup.
12. Gameplay Ingestion Flow
Example batch:
{
"integration_id": "pp_int_01K...",
"events": [
{
"event_id": "provider-event-8291",
"player_id": "pp_plr_prv_X72Q4...",
"type": "game_round_settled",
"game_id": "pp_game_01K...",
"bet": "10.00",
"win": "4.00",
"currency": "EUR",
"occurred_at": "2026-09-03T10:15:00Z"
}
]
}
Validation order:
- Validate OAuth access token.
- Resolve authenticated provider/client.
- Validate
pp_int_...belongs to that client. - Derive trusted provider/operator scope from integration configuration.
- Resolve alias only within that audience.
- Validate schema and permitted event types.
- Enforce idempotency on issuer/integration/event ID.
- Persist raw accepted event or append to the ingestion stream.
- Update loyalty asynchronously.
- Update analytics asynchronously.
13. Token Profiles
13.1 Player access token
The public/browser token should use the IAM subject, not contain the private PlayerPal UUID or detailed loyalty data.
{
"iss": "https://auth.playerpal.example",
"sub": "iam-user-83f2...",
"aud": "playerpal-player-api",
"azp": "playerpal-web",
"scope": "openid player:self loyalty:read",
"sid": "iam-session-id",
"iat": 1788428400,
"exp": 1788429000
}
The API maps iss + sub to the private player server-side.
13.2 Administrator token
{
"iss": "https://auth.playerpal.example",
"sub": "admin-iam-subject",
"aud": "playerpal-admin-api",
"organization_id": "pp_opr_01K...",
"roles": ["operator_admin"],
"scope": "openid reports:read campaigns:manage",
"exp": 1788429000
}
Fine-grained authorization remains server-side.
13.3 Provider machine token
{
"iss": "https://auth.playerpal.example",
"sub": "pp_prv_01K...",
"aud": "playerpal-ingestion-api",
"integration_id": "pp_int_01K...",
"scope": "activity:write batch:submit",
"exp": 1788429000
}
Recommended access-token lifetime: 5–15 minutes. Do not issue browser refresh tokens to embedded widgets.
14. Session Architecture
14.1 Player web application
- Use Authorization Code + PKCE.
- Prefer a backend-for-frontend (BFF) and secure
HttpOnly,Secure,SameSitecookies. - Do not store long-lived tokens in
localStorage. - Bind state and nonce to the login transaction.
- Rotate sessions following authentication-method changes.
14.2 Embedded widget
- Use a first-party PlayerPal popup for interactive authentication/linking.
- Use one-time authorization codes.
- Validate
postMessagesender and target origins exactly. - Return minimal status/result information.
- Do not depend on third-party cookies.
- Do not expose refresh tokens to the widget.
14.3 Machine clients
- No interactive sessions.
- Use short-lived access tokens.
- Authenticate with asymmetric keys.
- Support key overlap during rotation.
- Rate-limit per client/integration.
15. Proposed Data Model
15.1 Core tables
CREATE TABLE players (
id UUID PRIMARY KEY,
public_player_id VARCHAR(64) NOT NULL UNIQUE,
status VARCHAR(32) NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
activated_at TIMESTAMPTZ,
anonymised_at TIMESTAMPTZ
);
CREATE TABLE authentication_identities (
id UUID PRIMARY KEY,
player_id UUID NOT NULL REFERENCES players(id),
issuer VARCHAR(255) NOT NULL,
subject_ciphertext BYTEA NOT NULL,
subject_lookup_hmac BYTEA NOT NULL,
method VARCHAR(32) NOT NULL,
assurance_level VARCHAR(32) NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
last_used_at TIMESTAMPTZ,
revoked_at TIMESTAMPTZ,
UNIQUE (issuer, subject_lookup_hmac)
);
CREATE TABLE player_aliases (
id UUID PRIMARY KEY,
player_id UUID NOT NULL REFERENCES players(id),
audience_id UUID NOT NULL,
alias_type VARCHAR(32) NOT NULL,
external_alias VARCHAR(64) NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
UNIQUE (audience_id, external_alias)
);
CREATE UNIQUE INDEX uq_active_player_audience_alias
ON player_aliases(player_id, audience_id)
WHERE revoked_at IS NULL;
CREATE TABLE identity_links (
id UUID PRIMARY KEY,
player_id UUID NOT NULL REFERENCES players(id),
source_type VARCHAR(32) NOT NULL,
source_id UUID NOT NULL,
proof_type VARCHAR(64) NOT NULL,
assurance_level VARCHAR(32) NOT NULL,
consent_record_id UUID,
linked_at TIMESTAMPTZ NOT NULL,
linked_by UUID,
unlinked_at TIMESTAMPTZ
);
CREATE TABLE integrations (
id UUID PRIMARY KEY,
public_integration_id VARCHAR(64) NOT NULL UNIQUE,
provider_id UUID,
operator_id UUID,
subject_namespace VARCHAR(255) NOT NULL,
status VARCHAR(32) NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
15.2 Separation from loyalty/data tables
Loyalty and analytics should reference the internal player UUID, not IAM subjects or aliases:
loyalty_ledger.player_id → players.id
campaign_progress.player_id → players.id
activity_events.player_id → players.id
player_daily_metrics.player_id → players.id
16. API Surface
16.1 Player endpoints
GET /v1/me
GET /v1/me/loyalty
GET /v1/me/connections
POST /v1/me/connections/operator/start
POST /v1/me/connections/operator/confirm
DELETE /v1/me/connections/{connectionId}
POST /v1/me/auth-methods/passkeys
DELETE /v1/me/auth-methods/{methodId}
POST /v1/me/recovery-codes/rotate
16.2 Operator integration endpoints
POST /v1/operator-assertions/exchange
POST /v1/operator-connections/result-exchange
GET /v1/operator-players/{operatorAlias}/loyalty
POST /v1/operator-players/{operatorAlias}/provider-aliases
16.3 Provider endpoints
POST /v1/activity-batches
GET /v1/activity-batches/{batchId}
GET /v1/integrations/{integrationId}/status
16.4 Administrative endpoints
GET /v1/admin/organizations
POST /v1/admin/integrations
POST /v1/admin/integrations/{id}/keys
POST /v1/admin/integrations/{id}/rotate-key
GET /v1/admin/audit-events
17. Authorization Model
IAM roles may express broad personas:
player
operator_admin
provider_admin
affiliate_admin
playerpal_support
playerpal_platform_admin
PlayerPal permissions should express business operations:
loyalty.self.read
operator.player.loyalty.read
operator.player.alias.issue
provider.activity.write
provider.batch.status.read
reports.aggregate.read
reports.player.read
campaign.read
campaign.manage
integration.read
integration.manage
identity.connection.revoke
support.session.create
Authorization must evaluate:
- Principal.
- Organization.
- Integration.
- Audience.
- Resource owner.
- Permission.
- Data-sharing agreement/policy.
- Support or delegated-access context.
OPA, Cerbos or OpenFGA may be evaluated, but ordinary database-backed authorization is acceptable initially if rules and auditability remain explicit.
18. Loyalty and Analytics Behaviour
The private player is the owner of loyalty and analytical facts. External aliases are access handles, not separate wallets or players.
An operator may receive its permitted view using pp_plr_opr_...; a provider uses pp_plr_prv_.... Neither receives the private player ID or another audience's alias.
Whether activity or rewards exist before a player explicitly registers is a product-policy decision and must be specified separately. Authentication architecture must support both:
- Registration-first: loyalty begins at activation/effective date.
- Pre-enrolment accrual: operator-scoped pending value exists before claiming.
Do not accidentally enable historical reward claiming merely because identity records can be linked.
19. Recovery and Account Safety
Because no player email or telephone number is stored, recovery must be designed explicitly.
Required controls:
- Encourage at least two passkeys.
- Generate one-time recovery codes.
- Display recovery setup completion status.
- Require step-up authentication before adding/removing login methods.
- Notify active sessions in-product after credential changes.
- Allow revocation of lost devices.
- Define operator-assisted recovery with strong controls and cooling-off period.
- Never let an operator unilaterally take ownership of a PlayerPal account.
Social login can serve as a recovery method when already linked, but a social email match must not recover or link an account.
20. Security Requirements
20.1 Protocol
- Authorization Code + PKCE for public/browser clients.
- No OAuth implicit flow.
- No Resource Owner Password Credentials grant.
- Exact redirect URI matching.
- State and nonce validation.
- Short-lived access tokens.
- Rotating signing keys exposed through JWKS.
- Token audience validation by every API.
- Asymmetric client authentication for partners.
20.2 Data
- Encrypt sensitive mappings at application level.
- Use issuer-specific HMAC lookup values for external subjects.
- Store keys in a managed KMS/HSM.
- Redact tokens, assertions and subjects from logs.
- Prohibit player email/profile fields at API schema boundaries.
- Separate identity mappings from general analytics access.
- Use append-only audit events for linking and credential changes.
20.3 Abuse prevention
- Rate-limit registration, login, assertion exchange and token endpoints.
- Detect assertion replay.
- Apply brute-force protection and credential-stuffing controls.
- Require recent authentication for sensitive actions.
- Detect abnormal alias creation or connection attempts.
- Require administrative approval for high-risk integration/key changes.
21. Logging, Auditing and Observability
Audit:
- Player created.
- Authentication method added/revoked.
- Operator connection requested/approved/revoked.
- Alias issued/rotated/revoked.
- Identity linked/unlinked/merged/split.
- Partner key created/rotated/revoked.
- Administrator permission changed.
- Support session created.
Do not log:
- Access or refresh tokens.
- Raw social tokens.
- Operator assertions.
- Passkey credential material beyond safe identifiers.
- Raw external player subjects.
- Email/profile claims.
Metrics should include login success/failure, provider latency, assertion replay, alias-resolution failures, invalid audience submissions, batch rejection, token issuance and key age.
22. Recommended Component Architecture
Recommended logical components:
- IAM platform: Keycloak or ZITADEL.
- Player BFF: secure browser/session boundary.
- Identity Service: players, auth mappings, aliases and links.
- Integration Registry: operators, providers, credentials and namespaces.
- Authorization Service: organization/resource decisions.
- Ingestion API: authenticated batch acceptance.
- Event stream: decoupled processing, e.g. Redpanda/Kafka-compatible infrastructure.
- Loyalty engine: ledger/rules/campaign processing.
- Analytics pipeline/store: cohorts, retention and reporting.
23. IAM Product Decision
23.1 Recommended initial position
Use Keycloak initially if:
- Existing team experience is valuable.
- Self-hosting and protocol control are priorities.
- Complex enterprise federation is expected.
- Operational capability already exists.
Evaluate ZITADEL if:
- Managed operations are preferred.
- Its organization model materially simplifies partner onboarding.
- The no-email player proof of concept passes.
- Social login, passkey and machine identity behaviour meet requirements.
23.2 Required proof of concept
Whichever platform is selected must demonstrate:
- Passkey-only player creation without email.
- Social OIDC login with no stored email/profile attributes.
- Tokens without email/profile claims.
- Account linking only through authenticated explicit action.
- Separate admin and player policies.
- Enterprise admin SAML/OIDC.
- Machine client authentication using private keys.
- Export/migration of authentication mappings.
- Auditing without prohibited PII.
- Key rotation and session revocation.
24. Suggested Technology Stack
This specification is technology-neutral. A pragmatic implementation could use:
- IAM: Keycloak initially; ZITADEL as evaluated alternative.
- Backend services: Java/Kotlin, Go or TypeScript depending on team capability.
- Identity database: PostgreSQL.
- Cache/replay protection: Redis/Valkey.
- Event streaming: Redpanda or Kafka-compatible service.
- Key management: cloud KMS/HSM.
- Policy: application authorization initially; OPA/Cerbos/OpenFGA if complexity warrants.
- Observability: OpenTelemetry, Prometheus/Grafana and centralized structured logs.
- Frontend: BFF-based web application with popup +
postMessageintegration for embedded operator flows.
Avoid making the high-volume ingestion path depend synchronously on the IAM platform.
25. Non-Functional Requirements
Availability
- Player login and account linking should have independent availability objectives from gameplay ingestion.
- Normal gameplay and batch collection must continue if interactive authentication is unavailable.
- IAM outages must not invalidate already accepted batch-processing work.
Performance
- Token validation should normally use cached JWKS locally.
- Alias resolution should use indexed database/cache lookups.
- Batch ingestion should acknowledge after validation and durable acceptance, then process asynchronously.
- No IAM round trip per gameplay event.
Scalability
- Stateless APIs where practical.
- Partition event processing by integration/player where ordering is required.
- Idempotent processors.
- Horizontally scalable identity reads and ingestion.
Portability
- Product records reference PlayerPal player IDs, not IAM database IDs.
- IAM integration is behind an adapter/interface.
- Authentication identities retain issuer and subject semantics.
- Configuration and mappers are managed as code.
26. Testing Strategy
Unit tests
- Alias format and randomness.
- Namespace-scoped resolution.
- Claim filtering.
- Permission evaluation.
- Replay-cache behaviour.
- Token claim validation.
Integration tests
- Passkey registration and login.
- Each supported social provider.
- Add/remove authentication method.
- Operator assertion exchange.
- Popup and
postMessageorigin validation. - Machine token issuance.
- Provider batch authorization.
- Key rotation overlap.
Security tests
- OIDC state/nonce attacks.
- Authorization-code replay.
- Assertion replay.
- Cross-operator alias use.
- Cross-integration batch submission.
- Account-linking takeover attempts.
- JWT algorithm/key confusion.
- Redirect URI manipulation.
- Token leakage in logs and URLs.
- Email/PII injection into claims and API fields.
Privacy tests
- Verify no player email in IAM persistence.
- Verify no email in tokens.
- Verify no email in events/logs/traces.
- Verify partners cannot enumerate or correlate aliases.
- Verify deletion/anonymisation workflow.
27. Acceptance Criteria
The architecture is acceptable when:
- A player can register and recover access without PlayerPal storing email.
- A player can log in using a passkey and approved social providers.
- Social accounts are not merged through email matching.
- The player can connect two operators through explicit authenticated flows.
- Each operator receives a different player alias.
- A provider receives a different alias per provider–operator audience.
- Providers cannot submit another audience's alias.
- Gameplay batches process without interactive login or IAM calls per event.
- Loyalty and analytics consistently resolve to the private player.
- Administrators can use enterprise SSO.
- Partner backends use short-lived OAuth tokens with asymmetric authentication.
- The private PlayerPal ID and identity graph remain inaccessible to partners.
- No player email appears in PlayerPal persistence, tokens, logs or analytics.
28. Delivery Phases
Phase 1 — Foundation
- Select IAM platform through proof of concept.
- Establish players, authentication identities and alias schema.
- Implement passkey registration/login.
- Implement one social provider without email persistence.
- Implement Player BFF/session model.
Phase 2 — Operator connection
- Integration registry and partner keys.
- Signed operator assertions.
- Popup + one-time code +
postMessageflow. - Operator alias issuance and revocation.
- Consent and audit history.
Phase 3 — Provider activity
- Provider–operator aliases.
- Machine OAuth/private-key JWT.
- Batch ingestion and idempotency.
- Loyalty event processing.
- Initial analytics.
Phase 4 — Enterprise and scale
- Administrator enterprise SSO.
- Additional social providers.
- Account recovery hardening.
- Fine-grained authorization service.
- Alias rotation and identity split/merge tooling.
- Security/privacy certification work.
29. Open Decisions
The following require explicit product/architecture decisions:
- Keycloak or ZITADEL as the initial IAM platform.
- Exact interpretation of “must not store email”: no persistence versus no receipt anywhere.
- Supported social providers and their verified claim behaviour.
- Whether PlayerPal ID + password is offered as a fallback.
- Required player recovery experience.
- Whether points can accrue before explicit PlayerPal registration.
- Whether pre-registration balances can be claimed retrospectively.
- Whether provider aliases are eagerly provisioned or created on demand.
- Whether a provider-global alias is ever permitted; default should be provider–operator pairwise scope.
- Rules governing permitted cross-operator analytics.
- Retention after operator unlink or account deletion.
- Authorization implementation: application-native, OPA, Cerbos or OpenFGA.
30. Invariants for Implementers and Coding Agents
The following must not be weakened without an explicit architecture decision:
- Never expose the private internal player UUID.
- Never expose the player's
pp_plr_...identity to an operator/provider. - Never use
pp_opr_...orpp_prv_...as a player alias; they identify parties. - Use
pp_plr_opr_...for operator player aliases. - Use
pp_plr_prv_...for provider–operator player aliases. - Never derive an alias from PII or an external account ID.
- Never merge accounts using email, IP address, device fingerprint or timing.
- Never accept operator scope solely from an untrusted provider payload.
- Never place access tokens or signed assertions in URLs.
- Never require IAM access for every gameplay event.
- Never treat IAM roles as the complete PlayerPal authorization model.
- Never store player email in tokens, logs, databases, traces or analytics.
31. Compact Context for Claude or Another Coding Agent
PlayerPal is a privacy-preserving loyalty and analytics platform. Players have
a private PlayerPal identity (pp_plr_...) visible to themselves and PlayerPal,
but never to operators/providers. Each operator gets a random pairwise player
alias (pp_plr_opr_...). Each provider-operator integration gets another random
pairwise alias (pp_plr_prv_...). Party IDs such as pp_opr_..., pp_prv_... and
pp_int_... identify organizations/integrations and are not player aliases.
PlayerPal must not store player email, name or phone. Players can authenticate
using passkeys, approved social OIDC providers that yield a stable opaque
issuer+subject without persisted email, and optional PlayerPal-ID/password.
Never link identities by email or probabilistic signals.
Use Keycloak or ZITADEL for authentication protocols, social federation,
passkeys, MFA, sessions, enterprise SSO and machine OAuth. Keep the PlayerPal
identity graph, aliases, consent, loyalty ownership, analytics and business
authorization inside PlayerPal. The IAM subject is mapped server-side to a
private PlayerPal player.
Operators connect a player using a short-lived signed assertion plus explicit
player authentication and consent. Providers authenticate as OAuth machine
clients using private_key_jwt and submit provider-operator-scoped player aliases
in asynchronous gameplay batches. Normal game launches must not depend on a
synchronous PlayerPal call or interactive login.
32. Final Architecture Statement
PlayerPal uses a mature IAM platform to authenticate humans and machines, but maintains its own privacy-preserving player identity graph. A player can authenticate through social login, passkey or another supported method without PlayerPal storing email. PlayerPal resolves the authenticated principal to a private loyalty identity, issues unlinkable aliases to each operator/provider audience, processes gameplay asynchronously and applies all loyalty, reward, analytics and data-authorization logic inside the PlayerPal platform.
33. Confirmed Product Decisions Carried Forward
This section resolves decisions from the earlier PlayerPal product and loyalty work. Implementers must treat these as current requirements unless an ADR explicitly replaces them.
- PlayerPal infrastructure must never receive or store player email, name or telephone number. Prohibited PII must be removed at the social/IAM boundary before it reaches PlayerPal-controlled persistence, tokens, logs, traces, events or analytics.
- Normal game launch, wagering, round settlement and payout do not synchronously call PlayerPal.
- PlayerPal earning starts only after explicit registration/activation. PlayerPal does not create pre-registration shadow profiles or award historical points by default.
- An activity event is eligible only when
occurred_at >= connection.effective_fromand before any revocationeffective_at. - A browser cookie expiring does not break the persistent server-side identity link. Reauthentication is required for viewing, spending or managing the account, not for continued asynchronous earning.
- The canonical UUIDv7 and player-visible
pp_plr_...are never disclosed to an operator or provider. - Operators use
pp_plr_opr_...; providers use a distinctpp_plr_prv_...scoped to one provider–operator integration. - Provider/operator scope comes from authenticated integration configuration, credentials, certificate or upload location—not a free-text payload field alone.
- PlayerPal calculates points. Providers report factual activity and do not calculate the PlayerPal reward.
- The append-only points ledger is authoritative. Cached balances and projections are derived views.
34. Availability and Critical-Path Architecture
34.1 Non-negotiable failure rule
If every PlayerPal service is unavailable, the casino and provider must still be able to authenticate the player, launch the game, accept bets, settle rounds and pay winnings.
PlayerPal is therefore an asynchronous loyalty sidecar, not a transaction coordinator in the gaming path.
The solid path has no PlayerPal dependency. The dotted path may be delayed for minutes or hours without affecting play.
34.2 Required versus optional calls
| Moment | PlayerPal call | Required for game to continue? | Failure behaviour |
|---|---|---|---|
| Player explicitly joins/links | Interactive linking flow | No | Show unavailable/retry; game remains usable |
| Before or during every game launch | None | No | No impact |
| Each wager/round | None | No | Provider records activity locally |
| Batch upload | One call per batch/file | No | Retain and retry with backoff |
| Overlay status | Optional cached GET | No | Show neutral/unavailable; never block game |
| View balance/history | PlayerPal read | Only for PlayerPal UI | Show stale cached value or unavailable |
| Redeem portable points | PlayerPal command | Yes for redemption only | Do not redeem; casino gameplay remains unaffected |
34.3 Call-budget target
The minimum provider integration is:
- Receive an already-provisioned
pp_plr_prv_...in the operator's normal launch metadata, with no provider-to-PlayerPal lookup. - Add eligible activity to the provider's existing durable outbox or reporting store.
- Obtain and cache a machine token; token refresh is normally one call every 5–15 minutes per integration worker, not per player.
- Send one compressed batch containing hundreds or thousands of events.
- Poll batch status only after an ambiguous response; otherwise use the acceptance response or webhook.
No per-player session creation, per-launch resolution, per-round token request or synchronous loyalty calculation is permitted.
34.4 Failure matrix
| Failure | Provider action | PlayerPal recovery | Data-loss protection |
|---|---|---|---|
| DNS/network timeout | Keep batch in outbox; exponential backoff with jitter | None required | Durable provider outbox |
429 or 503 | Respect Retry-After; retry same idempotency key | Autoscale/recover | Same batch_id is safe |
| Request timeout after upload | Query by client batch_id or resend unchanged | Return existing receipt | Unique integration + batch ID |
| Invalid event among valid events | Prefer whole-batch accept with per-event async results, or reject structurally invalid envelope | Quarantine invalid event | Raw accepted envelope retained |
| PlayerPal processor outage | Stop consuming; do not reject already durable batches | Resume from stream/checkpoint | Durable stream/object storage |
| IAM outage | Reuse unexpired cached token; queue when expired | Recover independently | Gameplay never waits |
| Alias missing/revoked | Quarantine that event; continue others | Reconcile mapping/status | Per-event state and reason |
34.5 Provider outbox pattern
Providers should not need new distributed infrastructure solely for PlayerPal. An outbox table in the provider's existing database is sufficient initially.
CREATE TABLE playerpal_outbox (
id UUID PRIMARY KEY,
integration_id VARCHAR(64) NOT NULL,
event_id VARCHAR(128) NOT NULL,
payload JSONB NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL,
state VARCHAR(16) NOT NULL DEFAULT 'pending',
attempt_count INTEGER NOT NULL DEFAULT 0,
next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (integration_id, event_id)
);
The provider writes its normal settlement and the outbox item in the same local transaction where possible. A background worker batches and submits pending rows. PlayerPal availability is never part of the settlement transaction.
35. Stage-by-Stage Technology Architecture
35.1 Recommended lightweight deployment
Start as a modular monolith plus independent ingestion worker, not many microservices. The logical boundaries in this document do not require a separate deployable for each box.
| Stage | Responsibility | Initial technology | Scale-out option | Why |
|---|---|---|---|---|
| Edge | TLS, WAF, rate limiting, routing | Cloudflare or cloud load balancer/API gateway | Multi-region edge | Reject abuse before application compute |
| Player/operator web | Popup, settings, wallet UI | React/Next.js BFF | Regional replicas | Secure cookie sessions; no browser refresh token |
| Authentication | Social OIDC, passkeys, admin SSO, machine OAuth | Keycloak with PostgreSQL | Managed ZITADEL after POC | Existing team experience; protocols remain outside business code |
| Core API | Identity, consent, connections, operator reads | Go modular monolith | Split only measured hotspots | Low memory, fast startup, simple deployment |
| System of record | Players, links, aliases, consent, ledger | PostgreSQL | Read replicas/partitioning | Transactions, constraints and auditability |
| Ephemeral cache | Replay JTIs, rate limits, hot status | Valkey/Redis | Cluster/managed cache | Optional optimization, never source of truth |
| Batch edge | Validate envelope and durably acknowledge | Go HTTP service | Independent autoscaling | Small attack surface and predictable latency |
| Durable buffer | Decouple acceptance from processing | PostgreSQL inbox first | Redpanda/Kafka when volume requires | Avoid premature operational cost |
| Large batch landing | Compressed files and replay | S3-compatible object storage | Lifecycle/replication | Cheap durable bulk ingestion |
| Loyalty processing | Resolve, qualify, evaluate, ledger | Go worker + SQL rules/versioned config | Redpanda consumers/Flink for continuous KPIs | Deterministic idempotent processing first |
| Scheduling/backfills | Reconciliation and controlled batch jobs | Dagster | Managed scheduler | Better fit than Airflow for code/data asset pipelines at this stage |
| Realtime integration transforms | Only when partners require protocol mediation | Camel K optional | Dedicated connectors | Not needed for core batch-only path |
| Analytics | Daily aggregates and partner reporting | PostgreSQL initially | ClickHouse/BigQuery | Keep early cost low; move analytical scans off OLTP at scale |
| Observability | Metrics, traces, logs | OpenTelemetry + Prometheus/Grafana + Loki | Managed equivalents | Vendor-neutral telemetry with PII controls |
| Secrets/keys | Signing/encryption material | Cloud KMS + secret manager | HSM/high-assurance tier | Central rotation and audit |
35.2 Initial physical topology
35.3 Scaled topology
Adopt this only after measured throughput, retention or query isolation justifies it.
35.4 PostgreSQL-first versus stream-first trigger
Begin with a PostgreSQL inbox and workers if throughput is comfortably within database headroom and batches can be processed within the agreed freshness SLA. Introduce Redpanda when one or more are true:
- sustained ingestion exceeds the safe write/worker capacity of the inbox;
- multiple independent consumers need replayable events;
- consumer lag must be isolated independently;
- ordering/partitioning requirements become material;
- maintenance/backfills compete with live ingestion;
- the measured operational benefit exceeds running another stateful platform.
36. End-to-End User and Integration Flows
36.1 Flow 1 — New player joins from Bucks Casino settings
The player can close or fail the popup and still continue using Bucks Casino.
36.2 Flow 2 — Existing PlayerPal player links Bucks Casino at game open
The casino may show a PlayerPal entry point beside Big Bass Bonanza, but the actual game launch proceeds independently.
If a provider cannot accept a mid-session metadata update, earning begins with the next game session or with events the provider can unambiguously associate after effective_from. The integration contract must state this behaviour.
36.3 Flow 3 — Normal returning-player game launch
36.4 Flow 4 — Provider batch and processing
36.5 Flow 5 — View and redeem points
Viewing may tolerate a cached projection. Redemption may not.
If PlayerPal is unavailable, redemption is unavailable; this never blocks deposits, withdrawals, wagering or provider settlement.
36.6 Flow 6 — Unlink or opt out
- Player reauthenticates to PlayerPal.
- Player chooses the exact operator/provider relationship.
- PlayerPal records
revoked_at/effective_atand stops qualifying later events. - Aliases remain reserved and auditable; they are not reassigned.
- A status delta/webhook may notify the partner asynchronously.
- Delayed activity before the revocation timestamp may still qualify; activity at or after it does not.
37. Normative HTTP API
37.1 Conventions
- Base URL:
https://api.playerpal.com/v1. - JSON media type:
application/json; bulk uploads may useapplication/json, NDJSON or compressed object upload. - Timestamps: RFC 3339 UTC.
- Money: decimal strings plus ISO 4217 currency; never binary floats.
- Commands require
Idempotency-Keywith 24-hour minimum retention; activity IDs are retained for the full replay window. - Responses include
X-Request-Id; clients may provideX-Correlation-Idwith a non-PII value. 202 Acceptedmeans durably accepted, not loyalty-complete.- Partner APIs use OAuth audience-specific bearer tokens. Browser APIs use the BFF session cookie.
- Operator/provider identity and integration scope are derived from the authenticated client.
37.2 IAM endpoints
These are standards-based endpoints supplied by Keycloak/ZITADEL rather than custom PlayerPal business APIs.
GET /.well-known/openid-configuration
GET /oauth2/authorize
POST /oauth2/token
POST /oauth2/revoke
GET /oauth2/jwks
POST /oauth2/logout
Machine token request:
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=pp_int_01K...&
scope=activity:write&
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
client_assertion=<signed-private-key-jwt>
37.3 Create a link intent
POST /v1/link-intents
Authorization: Bearer <operator-machine-token>
Idempotency-Key: 3a0d...
Content-Type: application/json
{
"operator_assertion": "<signed-jwt>",
"return_origin": "https://www.buckscasino.example",
"requested_provider_integrations": ["pp_int_PRAGMATIC_BUCKS"]
}
{
"link_intent_id": "pp_lnk_01K...",
"link_url": "https://connect.playerpal.com/l/opaque-one-time-code",
"expires_at": "2026-09-03T16:05:00Z"
}
The operator_assertion is in the POST body, never a URL. requested_provider_integrations must be a subset of integrations already authorized for the authenticated operator; it does not grant scope.
37.4 Exchange completed link result
POST /v1/link-results/exchange
Authorization: Bearer <operator-machine-token>
Idempotency-Key: 83db...
Content-Type: application/json
{"result_code":"opaque-single-use-code"}
{
"connection_id": "pp_con_01K...",
"player_id": "pp_plr_opr_A91K7...",
"status": "active",
"effective_from": "2026-09-03T16:02:31Z",
"provider_aliases": [
{
"integration_id": "pp_int_PRAGMATIC_BUCKS",
"player_id": "pp_plr_prv_X72Q4..."
}
]
}
This is the single recommended alias-provisioning response. It avoids a separate call for every provider.
37.5 Retrieve aliases after configuration changes
This endpoint is optional and used when a provider integration is enabled after the player was linked.
POST /v1/operator-players/{operatorPlayerAlias}/provider-aliases:resolve
Authorization: Bearer <operator-machine-token>
Idempotency-Key: 71ad...
{"integration_ids":["pp_int_PRAGMATIC_BUCKS"]}
Only the authenticated operator's alias is accepted. The response never includes the private player or another operator's aliases.
37.6 Submit activity batch
POST /v1/activity-batches
Authorization: Bearer <provider-integration-token>
Idempotency-Key: batch_pragmatic_bucks_20260903_1600_0001
Content-Encoding: gzip
Content-Type: application/json
{
"batch_id": "batch_pragmatic_bucks_20260903_1600_0001",
"schema_version": "1.0",
"events": [
{
"event_id": "pragmatic-round-8291-settled-v1",
"player_id": "pp_plr_prv_X72Q4...",
"type": "round_settled",
"game_ref": "big-bass-bonanza",
"round_ref": "round-8291",
"bet": "10.00",
"win": "4.00",
"currency": "EUR",
"occurred_at": "2026-09-03T16:00:10Z"
}
]
}
The body does not contain a trusted operator_id or provider_id; those are resolved from the access token/client and integration registry.
HTTP/1.1 202 Accepted
Location: /v1/activity-batches/batch_pragmatic_bucks_20260903_1600_0001
Retry-After: 5
{
"batch_id": "batch_pragmatic_bucks_20260903_1600_0001",
"state": "accepted",
"received_events": 1,
"received_at": "2026-09-03T16:01:00Z"
}
37.7 Large-file batch
For large batches, avoid proxying the bytes through application instances.
POST /v1/activity-uploads
PUT <short-lived signed object URL>
POST /v1/activity-uploads/{uploadId}:complete
GET /v1/activity-batches/{batchId}
The signed URL is restricted to one object key, content type, size and short expiry. The completion call records checksum, schema version and client batch ID. Processing starts only after checksum verification.
37.8 Batch status
GET /v1/activity-batches/{batchId}
Authorization: Bearer <same-integration-token>
{
"batch_id": "batch_pragmatic_bucks_20260903_1600_0001",
"state": "processed_with_errors",
"counts": {
"received": 1000,
"processed": 997,
"duplicate": 2,
"rejected": 1
},
"error_report_url": "https://api.playerpal.com/v1/activity-batches/.../errors",
"completed_at": "2026-09-03T16:01:18Z"
}
37.9 Optional overlay status
GET /v1/integration-players/{providerPlayerAlias}/status
Authorization: Bearer <provider-integration-token>
{
"player_id": "pp_plr_prv_X72Q4...",
"status": "active",
"effective_at": "2026-09-03T16:02:31Z",
"cache_for_seconds": 300
}
Implementation is a single integration-scoped key lookup from a hot projection. It performs no write, creates no session and does not call IAM. Providers should cache it. If the call fails, the overlay displays status_unavailable or a neutral state, and the game continues. A cached provider state may be used as a hint but must not be shown as definitive current eligibility after its TTL.
37.10 Operator loyalty view
GET /v1/operator-players/{operatorPlayerAlias}/loyalty
Authorization: Bearer <operator-machine-token>
{
"player_id": "pp_plr_opr_A91K7...",
"points": 12450,
"tier": "gold",
"as_of": "2026-09-03T16:03:00Z"
}
37.11 Player self-service
GET /v1/me
GET /v1/me/wallet
GET /v1/me/activity?cursor=...
GET /v1/me/connections
DELETE /v1/me/connections/{connectionId}
POST /v1/me/auth-methods/passkeys
DELETE /v1/me/auth-methods/{methodId}
POST /v1/me/recovery-codes:rotate
POST /v1/me/redemptions
GET /v1/me/redemptions/{redemptionId}
37.12 Error envelope
{
"error": {
"code": "alias_not_valid_for_integration",
"message": "The player alias is not valid for this integration.",
"request_id": "req_01K...",
"retryable": false
}
}
Do not reveal whether an alias exists under another integration. Cross-audience attempts return the same response as an unknown alias.
38. Data Processing Pipeline
38.1 Idempotency keys
Use both levels:
batch uniqueness: (integration_id, batch_id)
event uniqueness: (integration_id, event_id)
ledger uniqueness: (campaign_rule_version_id, activity_event_id, ledger_action)
An identical retry returns the original receipt. A reused ID with a different checksum returns 409 idempotency_conflict.
38.2 Ordering
- Do not require global ordering.
- Partition/serialize only where a rule depends on per-player or per-round order.
- Model reversals as new immutable events referring to the original event.
- Allow late events within a configured window.
- Recompute projections from the ledger; never mutate history to hide corrections.
38.3 Raw retention and replay
Store a checksummed raw accepted envelope in object storage or immutable database storage before destructive normalization. Keep processing version, schema version and rule version so a controlled replay is reproducible. Apply retention and pseudonymisation policies to the raw layer as personal data.
39. Service-Level Objectives and Backpressure
Suggested initial objectives—not contractual promises until load-tested:
| Capability | Objective |
|---|---|
| Game launch/wager availability dependency on PlayerPal | None |
| Link and account APIs | 99.9% monthly |
| Batch acceptance API | 99.95% monthly |
202 latency for normal batch | p95 under 500 ms after body received |
| Activity-to-ledger freshness | p95 under 5 minutes |
| Optional status lookup | p95 under 100 ms at edge/cache |
| Duplicate financial/points ledger effects | Zero by design |
| Accepted-event loss | Zero within documented durability model |
Backpressure rules:
- return
429withRetry-Afterbefore overload becomes failure; - cap body size and event count per integration;
- isolate quotas per integration so one partner cannot starve others;
- pause consumers without losing accepted events;
- expose lag/freshness, not just HTTP availability;
- accept fewer larger batches within safe size limits rather than many tiny calls.
40. Minimum Integration Profiles
Profile A — Recommended provider integration
Provider supports receiving pp_plr_prv_... in launch metadata and submits asynchronous batches. This gives the strongest privacy and lowest call count.
Synchronous provider calls to PlayerPal per launch: 0
Synchronous provider calls per wager/round: 0
Batch calls: typically 1 per interval/size threshold
Profile B — Operator submits activity
Where a provider cannot change its integration, the operator may submit normalized activity using its authenticated operator context and operator-scoped alias. Provider provenance remains explicit in the event. This keeps PlayerPal outside the provider path but shifts data-contract responsibility to the operator.
Profile C — Object-file delivery
For legacy/high-volume providers, use compressed NDJSON/Parquet delivery to an integration-specific object prefix via signed upload or managed file exchange. A manifest contains batch ID, schema version, row count and checksum. PlayerPal asynchronously imports it.
Profile D — Optional overlay
The provider embeds a lightweight PlayerPal script or operator-controlled component. It may perform one cached status read for UX truth. The overlay is never necessary for play or earning ingestion and must fail open with respect to gameplay.
41. Deployment and Operational Boundaries
Deploy at least two independently scalable workloads:
- Interactive/core: BFF, account, linking, wallet and partner APIs.
- Ingestion/processing: bulk acceptance, inbox/stream consumers and loyalty projections.
They may share PostgreSQL initially but require separate connection pools, resource quotas and circuit breakers. Prefer separate schemas and database roles. At higher scale, give ingestion and analytics independent data stores so load cannot exhaust interactive account access.
Do not let these dependencies turn into hard runtime coupling:
- API token validation uses locally cached JWKS, not token introspection on every call.
- Ingestion does not synchronously call IAM, campaign administration or the analytics store.
- Loyalty workers do not call provider APIs while holding database transactions.
- Webhooks are advisory and retried; they are not the sole source of state.
- Valkey/Redis loss degrades cache/replay performance but does not lose identity or ledger truth.
42. Revised Delivery Plan
Phase 0 — Contract and failure proof
- Freeze identifiers, schemas and error codes in OpenAPI/JSON Schema.
- Build a provider simulator with timeouts, duplicates, late events and reversals.
- Prove the casino/provider play flow works while all PlayerPal endpoints are blocked.
- Prove repeated batches create exactly one ledger effect.
- Complete the no-email IAM/social-provider POC.
Phase 1 — Thin vertical slice
- Keycloak, passkey and one approved social method.
- Go modular monolith, PostgreSQL and optional Valkey.
- Bucks Casino linking flow.
- Pragmatic Play integration alias returned in the link exchange.
- PostgreSQL inbox, asynchronous worker and points ledger.
- Player wallet view.
Phase 2 — Production hardening
- Provider outbox reference implementation/SDK.
- Gzip batches, idempotency, quarantine and replay.
- Object-storage archive and large-file profile.
- Key rotation, quotas, audit, SLO dashboards and disaster recovery.
- Opt-out/revocation delta propagation.
Phase 3 — Scale only where observed
- Redpanda for independent replayable consumers.
- ClickHouse or BigQuery for analytical scale.
- Regional ingestion endpoints and replicated object landing.
- Policy engine only when authorization complexity exceeds maintainable application rules.
43. Additional Acceptance Criteria
The expanded design is acceptable only when all of the following pass:
- Blocking PlayerPal DNS/API access does not prevent Bucks Casino from launching or Pragmatic Play from running Big Bass Bonanza.
- A provider can operate with zero PlayerPal calls during launch and each round.
- A token endpoint outage does not lose activity; workers queue until authentication recovers.
- A timeout after batch upload can be resolved without duplicate points.
- Activity before
effective_fromdoes not earn, even when delivered in a later batch. - Activity after relationship revocation does not earn.
- Provider A/Bucks aliases cannot be used through Provider A/another operator or another provider.
- No error message discloses cross-integration alias existence.
- Link exchange can return all already-enabled provider aliases in one response.
- The definitive overlay active state is cached briefly and degrades to neutral when unavailable.
- Redis/Valkey loss does not destroy identity, connection or ledger state.
- Every ledger entry identifies source event, integration, rule version and reason.
44. Updated Coding-Agent Context
PlayerPal is asynchronous loyalty infrastructure, never part of the gaming
transaction path. Casino login, game launch, wagers, settlement and payouts
must continue if PlayerPal is completely unavailable. Operators pre-provision
provider-operator pairwise aliases and include them in normal provider launch
metadata. Providers write settled activity to their own durable outbox and
submit compressed idempotent batches later. There are no PlayerPal calls per
launch or per round.
Registration-first is mandatory by default: do not create shadow players,
pre-registration loyalty or retrospective earning. Qualify only events whose
occurred_at is within the connection effective window.
Use Keycloak initially for protocol authentication, a Go modular monolith plus
separate Go ingestion worker, PostgreSQL as the system of record/inbox/ledger,
Valkey only as an optional cache, and object storage for large/raw batches.
Introduce Redpanda and ClickHouse/BigQuery only when measured scale requires
them. Validate OAuth JWTs locally through cached JWKS; never call IAM for each
event.
Private player UUIDv7 and pp_plr_... never leave PlayerPal. Operator aliases
are pp_plr_opr_... and provider aliases are pp_plr_prv_... scoped to one
provider-operator integration. Never trust provider/operator scope from a
payload alone. PlayerPal never receives or stores player email, name or phone.
45. Player Account Bootstrap Methods
45.1 Definition
A bootstrap method is the first proof or credential ceremony used to create and secure a PlayerPal account before a pp_plr_... identifier exists. The entry point and the authentication method are separate concerns:
Entry point: PlayerPal / operator / provider / affiliate / reward partner
Bootstrap method: passkey / approved social OIDC / password / device key
Connection context: signed assertion or activation code
Result: private player + player-visible pp_plr_ ID + optional pairwise connection alias
An operator or provider assertion proves the local partner context. It does not, by itself, give that partner permanent ownership of the portable PlayerPal account.
45.2 Method comparison
| Method | Creates first account? | Recommendation | Protocols and services | Durable credential |
|---|---|---|---|---|
| Passkey | Yes | Default | WebAuthn/FIDO2; Keycloak or ZITADEL; PlayerPal Identity Service | WebAuthn credential |
| Approved social OIDC | Yes | Supported after POC | OIDC Authorization Code + PKCE; Keycloak/ZITADEL identity broker | Opaque issuer + subject |
| Partner assertion + passkey | Yes | Best embedded flow | Signed JWT/JWS assertion; WebAuthn; pairwise alias service | Passkey |
| Partner assertion + recovery secret | Yes | Controlled fallback | Signed JWT/JWS; CSPRNG secret; Argon2id; KMS | Recovery secret, then upgraded |
| Generated PlayerPal ID + password | Provisional only | Restricted fallback | Keycloak/ZITADEL credential flow; Argon2id; breached-password check | pp_plr_... + password |
| Player-chosen username + password | Technically yes | Do not launch initially | Canonical username index; Argon2id; rate limiting | Username + password |
| Native application device key | Yes | Mobile complement | Secure Enclave/Android Keystore; JWK/COSE challenge signing | Hardware-backed key |
| Existing-device QR approval | No; adds a device to an existing account | Recommended device-add flow | Device Authorization pattern; one-time nonce; polling or WebSocket | New session/passkey/device key |
| Partner activation code | Only after durable credential setup | Campaign/invitation context | Opaque single-use code; PostgreSQL/Valkey TTL; idempotent exchange | Passkey/password/device key |
All methods retain the global privacy rule: no player email, name or telephone number may cross into PlayerPal-controlled systems.
45.3 Bootstrap API primitives
POST /v1/registrations
GET /v1/registrations/{registrationId}
POST /v1/registrations/{registrationId}:complete
POST /v1/link-intents
POST /v1/link-results/exchange
POST /v1/device-connections
POST /v1/device-connections/{connectionId}:approve
POST /v1/activation-codes/exchange
POST /v1/me/auth-methods/passkeys
POST /v1/me/auth-methods/password
POST /v1/me/recovery-codes:rotate
POST /v1/registrations creates only short-lived server-side registration state. It does not create a player until a permitted bootstrap ceremony completes.
Example:
POST /v1/registrations
Idempotency-Key: 2f4d...
Content-Type: application/json
{
"method": "passkey",
"link_intent_id": "pp_lnk_01K...",
"locale": "en"
}
{
"registration_id": "pp_reg_01K...",
"status": "credential_required",
"expires_at": "2026-09-03T21:15:00Z",
"next": {
"type": "webauthn_create",
"options_endpoint": "/v1/registrations/pp_reg_01K.../webauthn-options"
}
}
45.4 Passkey bootstrap

- Player starts a short-lived registration transaction.
- IAM/PlayerPal returns WebAuthn creation options.
- The authenticator creates a credential after local user verification.
- The server validates origin, RP ID, challenge, signature and attestation policy.
- PlayerPal creates the private player and returns the player-visible
pp_plr_...through the authenticated PlayerPal UI.
Security requirements:
- resident/discoverable credentials where platform support permits;
- user verification required;
- exact relying-party origins;
- challenge is random, single-use and short-lived;
- PlayerPal never receives fingerprint, face data or device PIN;
- encourage a second passkey and recovery-code generation before setup is considered resilient.
45.5 Approved social OIDC bootstrap

- Player selects an approved social identity provider.
- IAM starts Authorization Code + PKCE.
- The provider authenticates the player.
- A claim-filtering boundary rejects/removes email, name, telephone number, profile picture and other prohibited claims before any PlayerPal-controlled component receives them.
- PlayerPal maps the stable opaque
issuer + subjectto a new private player.
Each social provider requires a release-blocking POC proving that prohibited claims are neither requested nor received. Default Keycloak/ZITADEL broker templates must not be assumed compliant. Accounts are never joined by matching email or profile data.
45.6 Operator/provider assertion plus passkey

This is the recommended Bucks Casino, Pragmatic Play or other embedded partner flow.
The integration identity determines whether the resulting alias is pp_plr_opr_... or a pp_plr_prv_... scoped to provider × operator. Direct provider entry must still have trusted operator scope when the provider serves multiple casinos.
45.7 Partner assertion plus recovery secret

This fallback supports a device that cannot create a passkey. After consent, PlayerPal creates the account and displays a high-entropy one-time recovery secret. The secret must be shown once, stored hashed, rate-limited and replaceable. The player must be prompted to upgrade to a passkey or password.
Partner authentication alone must never be sufficient to take over, recover or permanently control the PlayerPal account.
45.8 Generated PlayerPal ID plus password

The player cannot supply a PlayerPal ID before initial registration because it does not yet exist. Password knowledge also does not establish that the registrant is a unique person. A password-first flow is therefore allowed only as a restricted fallback:
- Create short-lived anonymous registration state.
- Player selects and confirms a password.
- Apply password strength and breached-password checks.
- Create a provisional account and generate
pp_plr_.... - Show the ID and recovery-code ceremony.
- Future authentication uses
pp_plr_... + password. - Keep reward redemption, points transfer, welcome bonuses and other economically valuable actions disabled.
- Upgrade the account only after a passkey, approved social OIDC identity, trusted partner assertion, existing-device approval or controlled activation proof is linked.
Passwords must use a modern memory-hard hash such as Argon2id with versioned parameters. Password reset requires recovery code, existing authenticated device/passkey, linked social method or strongly controlled operator-assisted recovery—not email or SMS. Password-only registration must never be described as enforcing one account per person.
45.9 Player-chosen username plus password

This is technically possible without explicitly asking for PII, but it is not recommended for the initial product. Usernames create enumeration, impersonation, moderation and accidental-PII risks and encourage reuse across sites. If introduced later:
- prevent email-shaped and telephone-shaped values;
- use a separate canonicalized lookup form;
- return uniform authentication errors;
- aggressively rate-limit availability and login checks;
- never use username as the loyalty/player primary key;
- still issue a separate private PlayerPal identity.
45.10 Native application device key

A native PlayerPal application may generate a non-exportable key in Apple Secure Enclave or Android hardware-backed Keystore, register the public key and sign challenges. This supports mobile registration, browser approval and redemption confirmation.
It is custom authentication surface area and must not silently replace WebAuthn/passkeys where the standard works. Define key attestation policy, device migration, revocation and loss recovery before launch.
45.11 Existing-device QR approval

QR approval does not create the first identity. It authorizes a new browser/device using an already authenticated device.
POST /v1/device-connections
Content-Type: application/json
{"new_device_public_key":"<jwk-or-cose-key>"}
The server returns a one-time QR payload containing an opaque connection ID and high-entropy secret. The existing device scans it, displays the new-device context, requires local user verification and calls:
POST /v1/device-connections/{connectionId}:approve
Authorization: <authenticated-player-session>
Idempotency-Key: 748a...
The QR payload must expire quickly, be single-use and contain no player ID, partner alias or bearer session token.
45.12 Partner activation code

An activation code carries invitation/campaign context and may start registration from an operator, provider, affiliate or reward partner.
POST /v1/activation-codes/exchange
Content-Type: application/json
{"code":"PP-7KRM-92TX"}
The server resolves partner and campaign scope from the stored code record, not from client-supplied fields. The code is random, single-use, short-lived, rate-limited and never a permanent credential. Account creation completes only after the player establishes a passkey, approved social identity, password or supported device key and approves any connection.
45.13 Bootstrap decision flow
45.14 Bootstrap invariants
- A
pp_plr_...is generated by PlayerPal; it is never accepted from a partner. - A PlayerPal ID cannot be used as the initial proof before it has been generated.
- Registration context expires and is distinct from the resulting account/session.
- Partner assertion and activation code provide context, not permanent player authentication.
- Every account must end with at least one player-controlled durable credential.
- Existing-device QR requires an already authenticated account.
- No bootstrap request, claim, assertion, log or error may contain player email, name or telephone number.
- Social identities are keyed by exact
issuer + subject, never email. - Adding or removing a bootstrap/authentication method requires recent step-up authentication.
- Completing partner-originated registration records explicit consent and
effective_frombefore activity can earn. - A password is a credential, not duplicate-resistant proof of a natural person.
- Password-only accounts remain provisional until a stronger bootstrap proof is linked.
- Registration completion is idempotent and may create at most one player for one registration transaction.
- PlayerPal never deduplicates or merges using email, IP address, browser/device fingerprint, timing or profile similarity.
46. Duplicate Accounts, Assurance and Controlled Merge
46.1 Security objective and limitation
Because PlayerPal does not collect a player's email address, name, telephone number or government identity, it cannot guarantee one natural person = one account. The design objective is narrower and testable:
- prevent accidental duplicates caused by retries and confusing journeys;
- prevent the same trusted bootstrap identity from creating several PlayerPal accounts;
- increase the cost of automated or promotional abuse;
- prevent low-assurance accounts from extracting economic value;
- provide a safe, auditable correction path when legitimate duplicates occur.
This is a privacy-preserving Sybil-resistance model, not civil-identity deduplication.

46.2 Duplicate-resistant bootstrap proofs
| Proof | Pseudonymous uniqueness scope | Duplicate resistance | Notes |
|---|---|---|---|
| Social OIDC | Exact issuer + subject | Strong within one social issuer/account | Never use email to link issuers |
| Operator assertion | operator_id + opaque_operator_subject | Strong within one operator | Subject must be stable and non-PII |
| Direct provider assertion | integration_id + opaque_provider_subject | Strong within provider × operator scope | Do not assume provider-wide identity when operator scope differs |
| Activation code | Code issuance record | Strong for one controlled invitation | Single-use; issuer determines eligibility |
| Existing authenticated device | Existing PlayerPal account | Very strong | Adds access to the existing account |
| Passkey/WebAuthn | Registered credential | Moderate for person-level deduplication | A person can create another credential/account |
| Password only | None beyond transaction controls | Weak | Creates a provisional account only |
For a repeatable external proof, PlayerPal stores a non-reversible lookup key:
bootstrap_lookup_key = HMAC-SHA-256(
active_pepper,
issuer_type || 0x00 || issuer_id || 0x00 || external_subject
)
Requirements:
- the raw partner subject is not used as a cross-party identifier;
- the lookup key is scoped by issuer/integration;
- the HMAC pepper is held in KMS/secret management and versioned for rotation;
- the database enforces uniqueness for active mappings;
- a repeated proof resolves to the existing mapping and never silently creates a second player;
- partner-provided scope is accepted only after authenticating the integration and validating the signed assertion.
Suggested constraint:
CREATE UNIQUE INDEX uq_bootstrap_identity_active
ON player_auth_identities (issuer_type, issuer_id, bootstrap_lookup_key)
WHERE status = 'active';
46.3 Idempotent registration state machine

The registration record owns the creation decision. POST retries do not own it.
POST /v1/registrations
GET /v1/registrations/{registrationId}
POST /v1/registrations/{registrationId}/bootstrap-results
POST /v1/registrations/{registrationId}:complete
All mutating calls require an Idempotency-Key. The service persists the request fingerprint, status and response. Reuse of the same key with a different request returns 409 idempotency_key_reused.
The completion transaction must atomically:
- lock the registration;
- verify it is unexpired and not already completed;
- insert or resolve the scoped bootstrap identity under a uniqueness constraint;
- create at most one private player when no mapping exists;
- attach the credential and initial assurance level;
- create the partner connection only after consent;
- persist the final response;
- add follow-up work to a transactional outbox.
Example provisional response:
{
"player_id": "pp_plr_7D4P6...",
"account_status": "provisional",
"assurance_level": "A0_PASSWORD_ONLY",
"capabilities": ["session:create", "credential:add", "partner:connect"],
"restricted_capabilities": [
"reward:redeem",
"points:transfer",
"welcome_bonus:claim"
],
"required_next_proofs": ["webauthn", "social_oidc", "partner_assertion"]
}
46.4 Assurance levels and capability gating

| Level | Minimum proof | Example capabilities | Restrictions |
|---|---|---|---|
A0_PASSWORD_ONLY | PlayerPal ID + password | Sign in, view setup, add credential, connect partner | No redemption, transfer or acquisition bonus |
A1_PLAYER_CONTROLLED | Verified WebAuthn/passkey or approved social OIDC | Normal account access and low-risk loyalty participation | Policy may step up high-value actions |
A2_PARTNER_CONFIRMED | A1 plus valid operator/provider relationship proof | Earn from confirmed integration; normal redemption subject to programme policy | Limited to authorised programme and jurisdiction |
A3_CORROBORATED | Multiple consistent independent trusted proofs | Higher-value actions where programme policy permits | Still subject to fraud, RG and compliance controls |
Assurance is not a permanent badge. Risk, revocation, exclusion, credential loss or partner disconnection may reduce effective capabilities. The authorization service evaluates the account level, requested action, programme, jurisdiction, risk decision and partner scope at execution time.
Recommended endpoint:
GET /v1/me/assurance
Authorization: Bearer <player-session>
{
"level": "A1_PLAYER_CONTROLLED",
"proofs": ["webauthn"],
"available_upgrades": ["operator_assertion", "provider_assertion"],
"capabilities": ["loyalty:view", "partner:connect", "reward:redeem:standard"]
}
46.5 Account discovery before creation

Before presenting final account creation, the interface asks Already have PlayerPal? and offers:
- use a passkey/WebAuthn credential;
- continue with the previously used approved social provider;
- enter PlayerPal ID and password;
- approve from an existing authenticated device;
- use a recovery code.
Successful authentication resumes the existing account and, when applicable, returns to the pending partner-consent journey. The interface never offers email/telephone lookup, never reveals whether an arbitrary PlayerPal ID exists and uses uniform authentication errors.
46.6 Risk controls without covert identity tracking
Risk controls reduce abuse but do not establish identity:
- IP/network velocity limits with short retention;
- per-registration, per-integration and per-bootstrap-key limits;
- adaptive CAPTCHA after elevated risk, not by default;
- proof-of-work or cooldowns for automated creation spikes;
- device-bound, short-lived risk cookies;
- breached-password screening and credential-stuffing protection;
- delayed or blocked economic capabilities for provisional accounts;
- coordinated gameplay, reward and campaign-abuse detection;
- manual review for high-value or conflicting merge attempts.
IP address, device fingerprint and browser characteristics must never become durable player-linking keys. Any fingerprinting proposal requires privacy/security review, documented retention and a demonstrated necessity.
46.7 Controlled account merge

Merging is a high-risk correction operation, not automatic deduplication. The player must independently authenticate both accounts with recent step-up authentication.
POST /v1/account-merges
POST /v1/account-merges/{mergeId}/source-proof
POST /v1/account-merges/{mergeId}/target-proof
GET /v1/account-merges/{mergeId}/preview
POST /v1/account-merges/{mergeId}:confirm
The preview must identify consequences without exposing private partner identifiers. Before commit, the merge service checks:
- duplicate welcome/campaign bonuses;
- already redeemed or reserved rewards;
- points provenance and incompatible programme wallets;
- conflicting operator/provider links;
- chargebacks, fraud holds and suspended accounts;
- self-exclusion, safer-gambling and jurisdiction restrictions;
- pending redemptions, transfers and ledger operations.
The merge transaction does not simply add balances. It applies programme-specific reconciliation rules, moves allowed credentials and aliases to the surviving account, records lineage, invalidates affected sessions and retires the secondary account. Ledger entries remain immutable; compensating entries record approved adjustments.
Automatic merge is prohibited for matches based on email, name, phone, IP, device/browser fingerprint, timing, gameplay similarity or profile similarity.
46.8 Operational and API failure behaviour
| Condition | API result | Required behaviour |
|---|---|---|
| Same idempotency key, same payload | Original status/response | Never repeat side effects |
| Same idempotency key, different payload | 409 idempotency_key_reused | Do not process |
| Bootstrap proof already mapped | Existing-account authentication path | Do not create a player |
| Registration expired | 410 registration_expired | Start a new ceremony |
| Insufficient proof | 403 step_up_required | Return permitted upgrade methods |
| Merge proof missing/expired | 403 dual_proof_required | Keep both accounts unchanged |
| Reconciliation conflict | 409 merge_conflict | Return safe conflict categories; require review |
| Client times out after commit | Retry returns stored result | No second account or alias |
46.9 Data model additions
CREATE TABLE player_registrations (
registration_id UUID PRIMARY KEY,
method VARCHAR(40) NOT NULL,
status VARCHAR(32) NOT NULL,
idempotency_key_hash BYTEA NOT NULL,
request_fingerprint BYTEA NOT NULL,
player_id UUID NULL,
final_response JSONB NULL,
expires_at TIMESTAMPTZ NOT NULL,
completed_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (idempotency_key_hash)
);
CREATE TABLE player_auth_identities (
auth_identity_id UUID PRIMARY KEY,
player_id UUID NOT NULL,
issuer_type VARCHAR(32) NOT NULL,
issuer_id UUID NOT NULL,
bootstrap_lookup_key BYTEA NOT NULL,
lookup_key_version SMALLINT NOT NULL,
assurance_level VARCHAR(32) NOT NULL,
status VARCHAR(24) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE player_account_merges (
merge_id UUID PRIMARY KEY,
source_player_id UUID NOT NULL,
target_player_id UUID NOT NULL,
source_proved_at TIMESTAMPTZ NULL,
target_proved_at TIMESTAMPTZ NULL,
status VARCHAR(32) NOT NULL,
reconciliation JSONB NULL,
confirmed_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
46.10 Acceptance criteria
- Concurrent completion requests for one registration create at most one player.
- The same active social, operator or integration-scoped provider subject cannot map to two players.
- A password-only registration produces
A0_PASSWORD_ONLYand cannot redeem, transfer or claim acquisition rewards. - Adding an approved stronger proof upgrades capabilities according to policy without changing
pp_plr_.... - Returning players can resume through credential-based discovery without email or telephone lookup.
- Merge requires valid, recent proof of both accounts and cannot proceed with unresolved value/compliance conflicts.
- Ledger history remains immutable through merge; adjustments use compensating entries.
- Retry after a committed-but-timed-out request returns the stored result.
- Logs and metrics contain registration/merge IDs and reason codes but no raw external subject or player PII.
- Automated tests prove that IP, device and profile similarity never trigger identity linking or account merge.
47. Responsive Player Experience Mockups
47.1 Purpose and presentation rules
The following mockups define how the duplicate-prevention and account-assurance flows appear on PlayerPal-controlled desktop and mobile surfaces. They are user-interface references, while Sections 45 and 46 remain authoritative for protocol and server behaviour.
All implementations must preserve these rules:
- the PlayerPal experience runs on a PlayerPal-controlled origin;
- the exact official PlayerPal wordmark is used, with the compact mark permitted on constrained surfaces;
- no screen requests or displays player email, name or telephone number;
- desktop and mobile communicate the same decision and capability state;
- mobile is a true responsive layout, not a scaled desktop screen;
- all controls maintain at least a 44 × 44 CSS-pixel target;
- text wraps inside its allocated component and never relies on clipping;
- focus indication, keyboard operation, screen-reader labels and WCAG 2.2 AA contrast are required;
- technical protocol names may appear in documentation/presentation framing, but player-facing copy remains plain;
- the UI does not claim that PlayerPal can prove one natural person has only one account.
47.2 Duplicate-prevention resolution

When a bootstrap proof already maps to an account, PlayerPal does not create another player. The player sees a neutral continuation screen rather than an identity-disclosure message.
User flow:
- Player completes a passkey, social OIDC or partner-originated bootstrap proof.
- PlayerPal calculates the issuer-scoped
bootstrap_lookup_key. - The uniqueness lookup resolves an existing authentication mapping.
- PlayerPal authenticates or steps up the player as required.
- The pending partner-consent journey resumes against the existing
pp_plr_....
The screen must not reveal account existence to an unauthenticated caller. “We found an existing sign-in for this proof” is shown only after successful control of that proof.
47.3 Assurance upgrade

The account-security view explains which capabilities are restricted and presents only eligible upgrades. For a provisional password account, Add a passkey starts WebAuthn/FIDO2 registration through Keycloak/ZITADEL or the selected IAM boundary. Approved social OIDC and trusted partner proofs may be offered according to policy.
The UI must obtain current assurance and capabilities from GET /v1/me/assurance. It must not infer an upgrade from a completed browser redirect alone. Until the authoritative response confirms the new level, show a neutral Verifying security upgrade state rather than a green success state.
47.4 Existing-account discovery

Account discovery appears before final account creation and offers credential-based recovery:
- passkey/WebAuthn;
- the previously used approved social provider using OIDC Authorization Code + PKCE;
- PlayerPal ID plus password;
- approval from an existing authenticated device;
- recovery code where enabled.
There is no email or telephone lookup. Authentication failures use uniform wording and timing. The partner link intent remains short-lived and resumes only after successful authentication.
47.5 Controlled account merge

The merge preview appears only after both accounts have been independently authenticated. It shows safe summaries of:
- credentials and partner connections that will move;
- rewards, reservations and balances requiring reconciliation;
- blocked or incompatible programme relationships;
- sessions that will be invalidated;
- the surviving PlayerPal ID and retirement of the secondary account.
The primary action remains disabled while reconciliation is pending or a compliance/value conflict exists. The UI must never expose another operator's or provider's pairwise alias.
47.6 Idempotent registration progress

The progress interface reflects server state rather than replaying client-side assumptions:
| UI state | Server state | Player-facing behaviour |
|---|---|---|
| Starting | Pending | Disable duplicate submission; retain the registration ID |
| Proof required | ProofRequired | Launch the selected WebAuthn/OIDC/assertion ceremony |
| Verifying | ProofVerified or RiskReview | Show neutral progress; do not promise account creation |
| Step-up required | StepUpRequired | Present allowed stronger proofs |
| Existing account | ExistingAccount | Authenticate/resume without creating a player |
| Completed | Completed | Continue using the stored final response |
| Expired | Expired | Explain expiration and start a new transaction |
The client sends one stable Idempotency-Key for retries of the same intent. A network timeout must lead to status retrieval or an identical retry—not a new registration transaction.
47.7 Responsive layout specification
| Property | Desktop reference | Mobile reference |
|---|---|---|
| Content model | Centered authentication panel | Full-height focused journey |
| Outer spacing | 40–48 px | 24–26 px |
| Control height | 56–58 px | 50–52 px |
| Card radius | 14–16 px | 12–14 px |
| Primary heading | 32–34 px | Dynamically fitted, 18–25 px |
| Body copy | 16–17 px | 13–14 px |
| Option label | 15–16 px | Dynamically fitted, minimum 10 px in presentation mockup |
| Primary action | Full panel width | Full available width, safe-area aware |
Production UI should reflow long localized labels onto a second line rather than reducing below the product's accessibility-approved minimum. Mockup rendering uses measured font bounds to guarantee that every label remains within its device screen and component.
47.8 Frontend integration endpoints
POST /v1/registrations
GET /v1/registrations/{registrationId}
POST /v1/registrations/{registrationId}/bootstrap-results
POST /v1/registrations/{registrationId}:complete
GET /v1/me/assurance
POST /v1/me/auth-methods/passkeys
POST /v1/device-connections
POST /v1/device-connections/{connectionId}:approve
POST /v1/account-merges
GET /v1/account-merges/{mergeId}/preview
POST /v1/account-merges/{mergeId}:confirm
All browser calls use short-lived PlayerPal sessions, CSRF protection appropriate to the session design, exact origin validation, correlation IDs and uniform safe error bodies. Partner callbacks receive one-time exchange codes; they never receive the private pp_plr_....