PostMessage Events Reference
Every event the EmbedAuth iframe emits over window.postMessage — types, payloads, when they fire, the origin check you must perform on all of them, and how to refresh tokens.
The embedded iframe communicates with your page exclusively through window.postMessage. This page is the complete reference for the events it emits and the payload each one carries.
Subscribing
Register one listener on window and branch on event.data.type:
window.addEventListener('message', (event) => {
// 1. ALWAYS verify the sender's origin first
if (event.origin !== 'https://cdn.embedauth.com') return;
// 2. Then branch on the event type
switch (event.data.type) {
case 'authframe:auth-success': /* ... */ break;
case 'authframe:auth-error': /* ... */ break;
// ...
}
});
Messages come from https://cdn.embedauth.com: the embedauth.com/embed/… URL you put in the iframe redirects to the widget's copy on EmbedAuth's CDN, and a message's origin is the page that sent it.
The origin check is mandatory. Any script running in any frame can post a message into your window; without the event.origin guard, a malicious page could send a forged auth-success and trick your code into treating an attacker as signed in. The origin check — combined with server-side token verification — is what makes the integration safe.
Event catalogue
Every message has the shape { type: string, data: object }.
authframe:auth-success
Fires when the user successfully signs in or signs up, by any method.
{
type: 'authframe:auth-success',
data: {
token: string; // RS256 JWT — verify on your server
refreshToken: string; // single-use; see "Refreshing tokens" below
user: { id: string; email: string; name: string | null; avatar: string | null };
}
}
The token is the only value you should trust, and only after your backend verifies it. The user object is a convenience for immediate UI; do not make authorization decisions from it.
Once the app has at least one domain configured, this message is posted with your page's exact origin as its target, and only when that origin is on the app's domain list. A page on any other site that frames the widget never receives it.
authframe:auth-error
Fires when a sign-in succeeded inside the widget but the tokens could not be handed over, because the page framing the widget is not on the app's domain list.
{
type: 'authframe:auth-error',
data: { error: string }
}
If you see this on your own site, add the page's domain on the app's Domains page in the dashboard. Ordinary failures — a wrong password, an expired code, a cancelled OAuth popup — are shown to the user inside the widget and are not posted to your page.
authframe:mode-change
Fires when the user switches between forms (for example from sign-in to sign-up).
{ type: 'authframe:mode-change', data: { mode: 'signin' | 'signup' | 'forgot-password' | 'magic-link' | 'otp' } }
Handy if you display a heading outside the frame that should track the current form.
authframe:resize
Fires whenever the form's content height changes — different forms, validation errors, and expanded sections all change it.
{ type: 'authframe:resize', data: { height: number } }
Apply height to your iframe element to eliminate inner scrollbars. See the resize helpers in the React and Vue guides.
authframe:loaded
Fires once the iframe has finished loading and is ready.
{ type: 'authframe:loaded', data: { clientId: string; mode: string } }
Use it to hide your own loading spinner or to confirm the embed initialized with the mode you expected.
Refreshing tokens
The access token expires (after one hour by default). To get a new one without asking the user to sign in again, post the refresh token to EmbedAuth:
const res = await fetch('https://embedauth.com/api/embed/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ clientId: 'YOUR_CLIENT_ID', refreshToken }),
});
// 200 → { token, refreshToken, user } 401 → sign the user in again
Each refresh token works exactly once. The response carries a new refresh token; store it in place of the old one. Presenting a refresh token that was already used is treated as theft: EmbedAuth revokes every refresh token descended from the same sign-in, and the user has to sign in again. If two tabs share one refresh token, make sure only one of them refreshes it.
The refresh token is a long-lived credential, so keep it out of localStorage. Holding it in memory works for a single page; better still, send it to your own server along with the access token and refresh from there.
Signing out
The sign-in widget has no sign-out button and sends no sign-out event: signing out happens in your app. Discard the tokens you hold and end your own session. To end a session on EmbedAuth's side too — so its refresh token stops working — revoke it from the app's Sessions page in the dashboard, or let users sign their own devices out with the account widget.
Reference table
| Event | Payload | Fires when |
|---|---|---|
authframe:auth-success | { token, refreshToken, user } | Sign-in or sign-up succeeds |
authframe:auth-error | { error } | Tokens withheld from a page not on the app's domain list |
authframe:mode-change | { mode } | User switches forms |
authframe:resize | { height } | Content height changes |
authframe:loaded | { clientId, mode } | Iframe finishes loading |
Next steps
- Turn
auth-successinto a verified session: Node.js, Python, Go. - Let signed-in users manage their own accounts: the account widget and its events.
- Not receiving events at all? See Troubleshooting.