A demo customer site embedding the XYZ AI chat widget
Credentials stay server-side — the appSecret never reaches the browser. The widget uses short-lived JWTs that are automatically refreshed.
Your Backend (server.js) XYZ AI API Browser Widget
| | |
| 1. POST /api/v1/auth/token| |
| { appId, appSecret, | |
| endUserId } | |
|--------------------------->| |
| | |
| { token, expiresAt } | |
|<---------------------------| |
| | |
| 2. Return JWT to browser | |
|---------------------------------------------------------->|
| | |
| | 3. Chat via widget |
| | Authorization: Bearer eyJ|
| |<-----------------------------|
| | |
| | Streaming response |
| |---------------------------->|
| | |
TOKEN REFRESH (automatic)
=========================
Proactive: ~60s before expiry, the widget calls fetchToken()
Reactive: on 401 response, the widget calls fetchToken() and retries once
| | |
| 4. Widget calls | |
| fetchToken() | |
|<-----------------------------------------------------------
| | |
| 5. POST /api/v1/auth/token| |
| { appId, appSecret, | |
| endUserId } | |
|--------------------------->| |
| | |
| { new token } | |
|<---------------------------| |
| | |
| 6. Return new JWT | |
|---------------------------------------------------------->|
| | |
| | Widget continues with |
| | new token seamlessly |
| | |
Paste this into any page to add the chat widget. A JWT token is required — get one from POST /api/v1/auth/token with appId, appSecret, and endUserId:
<script>
window.XYZChat = {
token: 'JWT_TOKEN',
host: 'https://xyzaimvp-production.up.railway.app',
chatHost: 'https://xyzai-mvp.vercel.app',
fetchToken: async function() {
var res = await fetch('/api/your-token-endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: 'END_USER_ID' })
});
return (await res.json()).data.token;
}
};
</script>
<script src="https://xyzaimvp-production.up.railway.app/widget/xyz-chat.js" async></script>