Ir al contenido

Integración browser-direct

Tu sk_live_* nunca debe vivir en el navegador. Para chats embebidos en una web seguí el diseño tipo Stripe payment_intent.client_secret: tu backend genera un token efímero csk_eph_* scoped a un session_id específico y se lo entrega al cliente. El browser usa ese token para hablar directo con el Canal API, sin tu intermediación en cada mensaje.

# ── Paso 1: tu backend genera el token con su sk_live_ ──────────────────
POST /api/v1/channel/session-tokens
Authorization: Bearer sk_live_YOUR_KEY
Content-Type: application/json
X-Overtaker-Version: 1
{
"session_id": "user-uuid-del-cliente", // requerido — scope del token
"ttl_minutes": 30 // opcional · default 30 · máximo 240 (4h)
}
# 200 OK — el token completo se muestra UNA SOLA VEZ
{
"token_id": "uuid-del-registro",
"token": "csk_eph_xxxxxxxxxxxxxxxx", // ← entregar al browser
"session_id": "user-uuid-del-cliente",
"expires_at": "2026-05-27T12:30:00Z"
}
# ── Paso 2: el browser usa el token para hablar directo con el Canal API ─
POST /api/v1/channel/message
Authorization: Bearer csk_eph_xxxxxxxxxxxxxxxx
Content-Type: application/json
X-Overtaker-Version: 1
{ "session_id": "user-uuid-del-cliente", "message": "Hola desde el browser" }
# ── Paso 3 (cuando termina): revocar manualmente ────────────────────────
DELETE /api/v1/channel/session-tokens/{token_id}
Authorization: Bearer sk_live_YOUR_KEY # ← debe ir con la sk_live_ dueña
X-Overtaker-Version: 1
# 204 No Content

Un csk_eph_* solo puede:

  • POST /channel/message con el mismo session_id del token (otro session_id → 403)
  • POST /channel/message/stream con el mismo session_id
  • GET /channel/conversations/{session_id}/messages de su sesión
  • GET /channel/ping

Los endpoints administrativos (rotación de webhook secret, sandbox, test de webhooks, GET lead, crear/revocar tokens) rechazan el token efímero con 403.

// 1) Endpoint en TU backend que pide el token con tu sk_live_:
// POST /api/chat/session → devuelve { token, session_id, expires_at }
// 2) En el componente del cliente
import { useEffect, useRef, useState } from 'react'
export function ChatWidget({ userId }: { userId: string }) {
const tokenRef = useRef<string | null>(null)
const sessionIdRef = useRef<string | null>(null)
const [reply, setReply] = useState('')
useEffect(() => {
// Pedir token a TU backend (que internamente llama a /channel/session-tokens)
fetch('/api/chat/session', { method: 'POST', body: JSON.stringify({ userId }) })
.then(r => r.json())
.then(({ token, session_id }) => {
tokenRef.current = token // ← en memoria, NO en localStorage
sessionIdRef.current = session_id
})
}, [userId])
async function send(text: string) {
const res = await fetch('https://api.overtaker.online/api/v1/channel/message', {
method: 'POST',
headers: {
'Authorization': `Bearer ${tokenRef.current}`,
'Content-Type': 'application/json',
'X-Overtaker-Version': '1',
},
body: JSON.stringify({ session_id: sessionIdRef.current, message: text }),
})
const data = await res.json() // { message_id, status: "received" }
// La respuesta del bot llega a TU webhook — desde ahí podés pushearla
// al browser via WebSocket / SSE / polling del history.
return data
}
return <button onClick={() => send('hola').then(d => setReply(JSON.stringify(d)))}>{reply || 'Enviar'}</button>
}