Protocol
Message envelope
Every message is a JSON-serializable object of the following shape:
interface PrivateKitMessage {
type: PRIVATE_KIT_MESSAGE_TYPES;
payload: {
connectionId: string;
// ...event-specific fields
};
}
See Message Contracts for the exact payload shape of each type.
connectionId
connectionId is a UUID generated by the iframe at mount time and sent to the host in PRIVATE_KIT_INIT. It is the canonical handle for this particular iframe instance:
- The host must echo it in every outgoing message (
PRIVATE_KIT_UPDATE_USERNAME). - The iframe silently drops any inbound message whose
payload.connectionIddoes not match the one it generated. - Hosts embedding multiple iframes simultaneously must track and route by
connectionIdto avoid cross-talk.
If the host remounts the iframe, a new connectionId is generated. Do not reuse a stored one.
authToken
Unlike Web3Kit, PrivateKit does not read the user's token from its URL. Every action message that requires authentication (PRIVATE_KIT_UPDATE_USERNAME, PRIVATE_KIT_UPDATE_EMAIL, PRIVATE_KIT_CONFIRM_EMAIL, PRIVATE_KIT_RESEND_EMAIL_CODE, PRIVATE_KIT_UPDATE_PHONE, PRIVATE_KIT_CONFIRM_PHONE, PRIVATE_KIT_RESEND_PHONE_CODE, PRIVATE_KIT_UPDATE_PASSWORD) carries the private authToken in its payload. The iframe forwards it as Authorization: Bearer <token> to the auth-central API.
- The host must use the user's private token (the JWT that grants access to
/private/api/v1/users). - Each action message can use a fresh token. After receiving
PRIVATE_KIT_AUTH_TOKEN_401, the host refreshes the token and re-sends the same action — there is no separate "set token" handshake.
Directions
| Direction | Message types |
|---|---|
| iframe → parent | PRIVATE_KIT_INIT, PRIVATE_KIT_USERNAME_UPDATED, PRIVATE_KIT_USERNAME_VALIDATION_ERROR, PRIVATE_KIT_EMAIL_UPDATED, PRIVATE_KIT_EMAIL_VALIDATION_ERROR, PRIVATE_KIT_EMAIL_CONFIRMED, PRIVATE_KIT_EMAIL_CONFIRMATION_ERROR, PRIVATE_KIT_EMAIL_CODE_RESENT, PRIVATE_KIT_PHONE_UPDATED, PRIVATE_KIT_PHONE_VALIDATION_ERROR, PRIVATE_KIT_PHONE_CONFIRMED, PRIVATE_KIT_PHONE_CONFIRMATION_ERROR, PRIVATE_KIT_PHONE_CODE_RESENT, PRIVATE_KIT_PASSWORD_UPDATED, PRIVATE_KIT_PASSWORD_VALIDATION_ERROR, PRIVATE_KIT_AUTH_TOKEN_401 |
| parent → iframe | PRIVATE_KIT_UPDATE_USERNAME, PRIVATE_KIT_UPDATE_EMAIL, PRIVATE_KIT_CONFIRM_EMAIL, PRIVATE_KIT_RESEND_EMAIL_CODE, PRIVATE_KIT_UPDATE_PHONE, PRIVATE_KIT_CONFIRM_PHONE, PRIVATE_KIT_RESEND_PHONE_CODE, PRIVATE_KIT_UPDATE_PASSWORD |
Origin handling
- The iframe currently posts with
targetOrigin: '*'. This will be tightened to a whitelist once allowed host origins are fixed. - The host must filter inbound messages by
event.origin === PRIVATEKIT_ORIGINregardless of what the iframe sends. - When the host posts to the iframe via
iframeRef.current.contentWindow.postMessage(...), set the second argument toPRIVATEKIT_ORIGINin production.'*'is acceptable only during local development.
Ordering
Within a single iframe lifetime the iframe always emits PRIVATE_KIT_INIT first. After that, every host-initiated action message produces exactly one outbound message from the iframe. Possible exchanges:
| Action message | Iframe response |
|---|---|
PRIVATE_KIT_UPDATE_USERNAME | one of USERNAME_UPDATED, USERNAME_VALIDATION_ERROR, AUTH_TOKEN_401 |
PRIVATE_KIT_UPDATE_EMAIL | one of EMAIL_UPDATED, EMAIL_VALIDATION_ERROR, AUTH_TOKEN_401 |
PRIVATE_KIT_CONFIRM_EMAIL | one of EMAIL_CONFIRMED, EMAIL_CONFIRMATION_ERROR, AUTH_TOKEN_401 |
PRIVATE_KIT_RESEND_EMAIL_CODE | one of EMAIL_CODE_RESENT, EMAIL_VALIDATION_ERROR (limitReached / unknown), AUTH_TOKEN_401 |
PRIVATE_KIT_UPDATE_PHONE | one of PHONE_UPDATED, PHONE_VALIDATION_ERROR, AUTH_TOKEN_401 |
PRIVATE_KIT_CONFIRM_PHONE | one of PHONE_CONFIRMED, PHONE_CONFIRMATION_ERROR, AUTH_TOKEN_401 |
PRIVATE_KIT_RESEND_PHONE_CODE | one of PHONE_CODE_RESENT, PHONE_VALIDATION_ERROR (limitReached / unknown), AUTH_TOKEN_401 |
PRIVATE_KIT_UPDATE_PASSWORD | one of PASSWORD_UPDATED, PASSWORD_VALIDATION_ERROR, AUTH_TOKEN_401 |
The host may repeat any action within the same iframe instance (e.g. after a 401-and-refresh, or after the user types a different value). The email-change and phone-change flows follow the same two-step shape: UPDATE_* → *_UPDATED → user copies the verification code → CONFIRM_* → *_CONFIRMED, with RESEND_*_CODE available between the first and last step. Password change is single-step — UPDATE_PASSWORD → PASSWORD_UPDATED directly, no out-of-band verification.
Logging
When the auth-central app is built with appConfig.demo.available = true, both sides emit verbose console.log traces:
- Iframe side: prefix
[private-kit], with direction tagin/out. - Reference host (
DemoPrivateKitTestPage): prefix[private-kit-demo], also taggedin/out.
In production builds (demo.available = false) all logging is a no-op.