Documentação de Integração Sloteflix
A Sloteflix é um agregador de jogos. Você integra uma vez e recebe acesso ao catálogo completo de provedores, com liquidação financeira em tempo real via Seamless Wallet — o saldo do jogador nunca sai da sua plataforma.
Visão geral
A integração tem exatamente duas metades. Entender essa divisão é a única coisa que realmente importa antes de escrever código:
| Metade | Direção | Quem implementa | O que faz |
|---|---|---|---|
| API Sloteflix | Você → Sloteflix | Nós (já pronto) | Listar catálogo, listar provedores, abrir jogo, free spins. |
| Seamless Wallet | Sloteflix → Você | Você | Debitar aposta, creditar prêmio, estornar, consultar saldo. |
Como funciona
Fluxo completo de uma rodada, do clique do jogador até o crédito do prêmio:
┌──────────┐ ┌────────────────┐ ┌─────────────┐ ┌──────────┐
│ Jogador │ │ SUA PLATAFORMA│ │ SLOTEFLIX │ │ PROVEDOR │
└────┬─────┘ └───────┬────────┘ └──────┬──────┘ └────┬─────┘
│ clica no jogo │ │ │
│─────────────────────▶│ │ │
│ │ GET games/game_launch │ │
│ │───────────────────────▶│ │
│ │ │ launch (interno) │
│ │ │───────────────────▶│
│ │ { "game_url": ... } │ │
│ │◀───────────────────────│ │
│ redirect / iframe │ │ │
│◀─────────────────────│ │ │
│ │ │ │
│ ── o jogo abre e o jogador gira o rolo ── │ │
│ │ │ │
│ │ │◀── bet ────────────│
│ POST /api/sloteflix_api { method:"transaction", type:"bet" } │
│ │◀───────────────────────│ │
│ │ { status:1, balance } │ │
│ │───────────────────────▶│ │
│ │ │─── ok ────────────▶│
│ │ │ │
│ │ │◀── win ────────────│
│ POST /api/sloteflix_api { method:"transaction", type:"win" } │
│ │◀───────────────────────│ │
│ │ { status:1, balance } │ │
│ │───────────────────────▶│ │
│ saldo atualizado │ │ │
│◀─────────────────────│ │ │
- Launch — você pede a URL do jogo passando
agent_code,agent_tokene ouser_iddo seu jogador. - Sessão — a Sloteflix cria uma sessão no provedor amarrada ao par
agent_id+user_id. Você não gerencia sessão. - Liquidação — cada evento financeiro do provedor vira um POST no seu endpoint seamless.
- Fallback de distribuidor — o mesmo
game_codepode existir em mais de um distribuidor. A Sloteflix tenta em ordem de prioridade até um responder. Transparente para você.
Credenciais
Ao ser cadastrado como agente você recebe quatro valores. Todos são obrigatórios em algum ponto da integração:
| Credencial | Tipo | Onde é usada |
|---|---|---|
agent_code |
string | Identifica sua operação. Vai em quase toda requisição e em todo callback seamless. |
agent_token |
string(32) | Autentica chamadas de jogo (launch, free spins). Também chega nos callbacks para você validar a origem. |
agent_secret_key |
string(32) | Junto com o token, gera o Bearer da autenticação JWT. Nunca exponha no front-end. |
endpoint |
url | A base URL da sua plataforma. É onde a Sloteflix vai bater o seamless. Configurável no painel. |
agent_token ou agent_secret_key em código de navegador.
A chamada de launch tem que sair do seu backend. Se o token vazar, qualquer pessoa
abre jogos em nome dos seus jogadores.
Ambientes & URLs
Base URL da API Sloteflix
Todos os endpoints que você chama ficam sob o prefixo /api/v1:
https://{SLOTEFLIX_HOST}/api/v1
O host exato é entregue pelo time de integração no momento do cadastro. Não faça hardcode — deixe em variável de ambiente.
URL do seu endpoint seamless
A Sloteflix monta o destino do callback a partir do campo endpoint do seu agente,
aplicando exatamente estas três transformações:
- substitui o domínio
lumosgaming.comporconnectionslm.com(roteamento interno de rede); - remove a barra final, se houver;
- concatena o caminho fixo
/api/sloteflix_api.
endpoint configurado : https://minhaoperacao.com callback resolvido : https://minhaoperacao.com/api/sloteflix_api
/api/sloteflix_api é fixo e não configurável. Você define só a base
(esquema + host + porta opcional). Salve o endpoint sem barra no final e sem path.
Orçamento de latência — leia isto
O cliente HTTP que dispara o seamless é agressivo de propósito: o jogador está esperando o rolo girar. Estes números são do código de produção, não uma recomendação:
| Parâmetro | Valor | Consequência |
|---|---|---|
| Timeout de conexão | 100 ms | TLS handshake lento já queima a tentativa. |
| Timeout de resposta | 600 ms | Seu handler tem que responder em menos de 600 ms. |
| Tentativas | 3 | Sem espera entre elas. Header X-Attempt traz o número. |
| Após 3 falhas | status: 15 | Tratado como Timeout (OP_47) — a rodada é rejeitada. |
600 ms é o orçamento total, incluindo rede, TLS, framework e banco. Na prática mire em < 150 ms no p99.
O que quebra integrações, na ordem: consulta de saldo sem índice, cold start de serverless, middleware de sessão/CSRF no caminho, e log síncrono. Tire tudo isso da rota do seamless.
Todas as tentativas chegam com estes headers:
Content-Type: application/json Accept: application/json Connection: keep-alive X-Attempt: 1 # 1, 2 ou 3
Quickstart
-
Receba suas credenciais
Peça ao time de integração o
agent_code,agent_token,agent_secret_keye o host da API. Guarde em variáveis de ambiente. -
Configure seu
endpointInforme a base URL da sua plataforma (ex.:https://minhaoperacao.com). É para lá que vão os callbacks. Veja Painel de configuração. -
Implemente
POST /api/sloteflix_apiUm único endpoint, roteado pelo campomethod. Comece poraccount_details,user_balanceetransaction— sem esses três nenhum jogo abre. -
Sincronize o catálogo
Chame
GET games/alle persista os jogos. Rode de novo periodicamente — o catálogo muda. -
Abra o primeiro jogo
Chame
GET games/game_launchdo seu backend e redirecione o jogador para agame_urlretornada.
# 1) confirme que o catálogo responde
curl -s "https://{SLOTEFLIX_HOST}/api/v1/games/games-status" | head -c 300
# 2) abra um jogo (troque game_id por um game_code do catálogo)
curl -s -G "https://{SLOTEFLIX_HOST}/api/v1/games/game_launch" \
--data-urlencode "agent_code=meu_agente" \
--data-urlencode "agent_token=$SLOTEFLIX_AGENT_TOKEN" \
--data-urlencode "game_id=vs20olympgate" \
--data-urlencode "type=CHARGED" \
--data-urlencode "currency=BRL" \
--data-urlencode "lang=BR" \
--data-urlencode "demo_agent=0" \
--data-urlencode "user_id=1042"
use Illuminate\Support\Facades\Http;
$response = Http::withQueryParameters([
'agent_code' => config('sloteflix.agent_code'),
'agent_token' => config('sloteflix.agent_token'),
'game_id' => 'vs20olympgate',
'type' => 'CHARGED',
'currency' => 'BRL',
'lang' => 'BR',
'demo_agent' => 0,
'user_id' => (string) $player->id,
])->get(config('sloteflix.base_url') . '/games/game_launch');
if ($response->successful() && $response->json('game_url')) {
return redirect()->away($response->json('game_url'));
}
report(new RuntimeException('Sloteflix launch falhou: ' . $response->body()));
return back()->withErrors('Jogo indisponível no momento.');
const params = new URLSearchParams({
agent_code: process.env.SLOTEFLIX_AGENT_CODE,
agent_token: process.env.SLOTEFLIX_AGENT_TOKEN,
game_id: 'vs20olympgate',
type: 'CHARGED',
currency: 'BRL',
lang: 'BR',
demo_agent: '0',
user_id: String(player.id),
});
const res = await fetch(`${process.env.SLOTEFLIX_BASE_URL}/games/game_launch?${params}`);
const json = await res.json();
if (!res.ok || !json.game_url) throw new Error(`launch falhou: ${JSON.stringify(json)}`);
return json.game_url;
import os, requests
params = {
"agent_code": os.environ["SLOTEFLIX_AGENT_CODE"],
"agent_token": os.environ["SLOTEFLIX_AGENT_TOKEN"],
"game_id": "vs20olympgate",
"type": "CHARGED",
"currency": "BRL",
"lang": "BR",
"demo_agent": 0,
"user_id": str(player.id),
}
r = requests.get(
f"{os.environ['SLOTEFLIX_BASE_URL']}/games/game_launch",
params=params, timeout=10,
)
r.raise_for_status()
game_url = r.json()["game_url"]
Convenções
Valores monetários
Apostas e prêmios trafegam como números decimais na moeda do jogador — não em centavos.
A persistência interna é DECIMAL(20,2), ou seja duas casas decimais.
{ "bet": 1.50, "win": 0 } // ✅ R$ 1,50
{ "bet": 150, "win": 0 } // ❌ isso é R$ 150,00, não R$ 1,50
Moeda
A moeda é fixada por agente (currency, padrão BRL) e informada no launch.
Os callbacks seamless não repetem a moeda em todos os métodos — trate a moeda do agente
como a moeda de todas as transações daquele agente.
Identificadores
| Campo | Formato | Observação |
|---|---|---|
user_id | string|int | É o seu ID de jogador. Você envia no launch e recebe de volta em todo callback. Não pode ser 0 nem vazio. |
game_id | string | No launch é o game_code do catálogo, não o id numérico. |
game | string | Nos callbacks, o código do jogo daquela rodada. |
transaction_id | string | Prefixado pelo distribuidor: timeless-123, betby-456. Globalmente único — use como chave de idempotência. |
round_id | string | Agrupa aposta e prêmio da mesma rodada. Também prefixado. |
session_id | string | Token da sessão de jogo, gerado pela Sloteflix. |
distributor | string | Origem da transação: timeless, betby, clone, playgaming, sourcepark… |
Códigos HTTP
Nas rotas da API Sloteflix: 200 em sucesso, 400 em erro de
validação/negócio, 403 em token administrativo inválido, 404 quando o recurso não existe.
HTTP 200 e sinalize o resultado
pelo campo status do corpo JSON. Veja Códigos de status.
Autenticação
Existem três esquemas em uso. Qual usar depende do endpoint:
| Esquema | Como enviar | Endpoints |
|---|---|---|
| Par de agente | agent_code + agent_token como parâmetros |
launch, free spins, get-agent-data |
| Bearer JWT | Authorization: Bearer <access_token> |
games/provider |
| Bearer básico | Authorization: Bearer base64("token:secret") |
authentication, clone status |
games/all,
games-status, to-add,
list-clone) hoje não exigem autenticação. Ainda assim, chame-os do backend:
o contrato pode endurecer sem aviso e você não quer descobrir isso em produção.
Obter o token JWT
Concatene agent_token e agent_secret_key com dois-pontos,
codifique em Base64 e envie como Bearer. A resposta é um JWT para as rotas protegidas.
Headers
| Header | Valor | |
|---|---|---|
Authorization | OBRIG. | Bearer base64(agent_token + ":" + agent_secret_key) |
TOKEN=$(printf '%s:%s' "$SLOTEFLIX_AGENT_TOKEN" "$SLOTEFLIX_AGENT_SECRET" | base64)
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/auth/authentication" \
-H "Authorization: Bearer $TOKEN"
use Illuminate\Support\Facades\Http;
$basic = base64_encode(config('sloteflix.agent_token') . ':' . config('sloteflix.agent_secret_key'));
$response = Http::withHeaders(['Authorization' => "Bearer {$basic}"])
->post(config('sloteflix.base_url') . '/auth/authentication');
abort_unless($response->successful(), 502, 'Sloteflix auth falhou');
$jwt = $response->json('access_token');
const basic = Buffer
.from(`${process.env.SLOTEFLIX_AGENT_TOKEN}:${process.env.SLOTEFLIX_AGENT_SECRET}`)
.toString('base64');
const res = await fetch(`${process.env.SLOTEFLIX_BASE_URL}/auth/authentication`, {
method: 'POST',
headers: { Authorization: `Bearer ${basic}` },
});
const { access_token } = await res.json();
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "bearer"
}
// Bearer ausente, não-Base64, ou sem os dois-pontos
{ "status": false, "error": "INVALID_TOKEN" }
// par token:secret não corresponde a nenhum agente
{ "status": false, "error": "INVALID_AGENT" }
401.
Launch Game
O endpoint central da integração. Devolve uma URL pronta para você abrir em iframe ou redirecionar o jogador. A Sloteflix resolve o distribuidor, cria a sessão e assina a URL.
Parâmetros (query string)
| Parâmetro | Tipo | Descrição | |
|---|---|---|---|
agent_code | OBRIG. | string | Seu código de agente. |
agent_token | OBRIG. | string | Seu token. Validado contra o agent_code. |
game_id | OBRIG. | string | O game_code do catálogo. Não o id numérico. |
type | OBRIG. | string | Modo da sessão. Use CHARGED para dinheiro real. |
currency | OBRIG. | string | ISO 4217 — ex.: BRL. Precisa bater com a moeda do agente. |
lang | OBRIG. | string | Idioma da interface do jogo — ex.: BR, EN. |
demo_agent | OBRIG. | 0 | 1 | 0 = dinheiro real, 1 = demo. Precisa estar presente mesmo valendo 0. |
user_id | OBRIG. | string|int | Seu ID de jogador. Rejeitado se vazio ou 0. |
force_demo | opcional | bool | Força o caminho demo, mesmo com demo_agent=0. |
rtp | opcional | int | RTP alvo, quando o distribuidor suporta. Ignorado caso não. |
demo_agent é obrigatório na validação. Enviar demo_agent=0 é
correto e significa dinheiro real; omitir o parâmetro devolve erro de validação.
Resposta
{
"game_url": "https://provider.example/launch?token=eyJhbGciOi...&lang=BR"
}
// falta um parâmetro obrigatório (formato padrão do validador Laravel)
{ "game_id": ["The game id field is required."] }
// user_id vazio ou igual a 0
{ "error": "Invalid user ID" }
// agent_code não existe
{ "error": "Agent not found" }
// agent_token não corresponde ao agent_code
{ "error": "Invalid agent token" }
// jogo inexistente, inativo, ou nenhum distribuidor conseguiu abrir
{ "status": false, "error": "INVALID_GAME" }
Formatos de erro não são uniformes neste endpoint. Você pode receber
{"error": "..."}, {"status": false, "error": "CODE"}, ou um mapa de erros de
validação — todos com HTTP 400.
Trate defensivamente: qualquer resposta sem game_url é falha. Não faça parsing por
formato específico.
Resolução de distribuidor
Internamente o launch percorre esta lógica. Você não controla nada disso, mas conhecer explica comportamentos:
- Busca o jogo por
game_codecomstatus = 1(ativo). - Se
demo_agentouforce_demo: exige que o jogo tenha origem própria (is_source) e abre pelo distribuidor de clone. - Caso contrário, lista os distribuidores ativos do jogo ordenados por
prioritydecrescente. - Se o agente tem
is_cloned = false, ou se o provedor é Pragmatic Play, o distribuidorcloneé removido da lista. - Tenta cada distribuidor em ordem —
clone,timeless,playgaming,sourcepark— e retorna a primeira URL obtida. - Se nenhum responder:
INVALID_GAME.
INVALID_GAME significa "não consegui abrir agora", não necessariamente
"esse jogo não existe". Pode ser indisponibilidade momentânea de um distribuidor. Não
remova o jogo do seu catálogo por causa de um único erro — use
games-status como fonte de verdade.
Listar jogos
Catálogo completo de jogos ativos, com todos os metadados. É o endpoint de sincronização.
{
"status": true,
"games": [
{
"id": 1841,
"provider_id": 12,
"game_server_url": null,
"game_id": "1301",
"game_name": "Gates of Olympus",
"game_code": "vs20olympgate",
"game_type": "slot",
"description": null,
"cover": "https://cdn.example/games/vs20olympgate.png",
"status": "1",
"technology": "html5",
"has_lobby": 0,
"is_mobile": 1,
"has_freespins": 1,
"has_tables": 0,
"only_demo": 0,
"distribution": "slots",
"created_at": "2024-03-02T18:11:04.000000Z",
"updated_at": "2025-06-18T09:42:57.000000Z"
}
]
}
Esquema do jogo
| Campo | Tipo | Descrição |
|---|---|---|
id | int | ID interno da Sloteflix. Não use no launch. |
provider_id | int | Referencia provedores. |
game_id | string | ID do jogo no provedor original. |
game_name | string | Nome de exibição. |
game_code | string | Único. É o que você passa como game_id no launch. |
game_type | string|null | Ex.: slot, live, crash. |
cover | string | URL da arte. Espelhe no seu CDN se for servir em escala. |
status | string | "1" = ativo. Este endpoint já filtra por ativos. |
technology | string|null | Ex.: html5. |
has_lobby | 0 | 1 | O jogo tem lobby próprio. |
is_mobile | 0 | 1 | Compatível com mobile. |
has_freespins | 0 | 1 | Aceita a API de free spins. |
has_tables | 0 | 1 | Jogo de mesa. |
only_demo | 0 | 1 | Só permite modo demo. |
distribution | string | Categoria de distribuição — útil para agrupar na sua vitrine. |
game_code, nunca por id. E não apague jogos
que saíram da lista: marque como inativos, para não perder histórico de transações.
Status dos jogos
Versão leve: só os game_code ativos. Perfeita para reconciliação frequente — compare com o
que está ativo na sua base e ajuste a vitrine.
{
"status": true,
"games": ["vs20olympgate", "vs20fruitsw", "spribe_aviator", "pgs_fortunetiger"]
}
Filtrar catálogo
Recebe uma lista de game_code e devolve os registros completos apenas dos que estão ativos.
Use para hidratar códigos novos sem baixar o catálogo inteiro.
Corpo
| Campo | Tipo | Descrição | |
|---|---|---|---|
games | OBRIG. | string[] | Lista de game_code. |
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/games/to-add" \
-H "Content-Type: application/json" \
-d '{"games":["vs20olympgate","vs20fruitsw"]}'
{
"status": true,
"games": [
{ "id": 1841, "game_code": "vs20olympgate", "game_name": "Gates of Olympus", "...": "..." }
]
}
games é lido diretamente do corpo, sem validação. Enviar um corpo sem esse campo,
ou com um valor que não seja array, gera erro 500. Sempre envie um array — mesmo vazio.
Listar provedores
Todos os provedores ativos. Requer JWT — pegue em authentication.
Headers
| Header | Valor | |
|---|---|---|
Authorization | OBRIG. | Bearer <access_token> |
curl -s "https://{SLOTEFLIX_HOST}/api/v1/games/provider" \
-H "Authorization: Bearer $JWT"
$response = Http::withToken($jwt)
->get(config('sloteflix.base_url') . '/games/provider');
$providers = $response->json('providers');
{
"status": true,
"providers": [
{ "id": 12, "name": "Pragmatic Play", "code": "pragmatic-play", "status": 1 }
]
}
Jogos clone
"Clone" é uma trilha de distribuição alternativa para parte do catálogo. Quando o agente tem
is_cloned = true, o launch prioriza essa trilha (exceto para Pragmatic Play, sempre excluída).
Estes endpoints deixam você inspecionar e alternar o comportamento.
Listar jogos clone
Retorna os jogos disponíveis pela trilha de clone. O formato acompanha a resposta do distribuidor de clone.
Consultar flag de clone
Headers
| Header | Valor | |
|---|---|---|
Authorization | OBRIG. | Bearer base64(agent_token + ":" + agent_secret_key) |
TOKEN=$(printf '%s:%s' "$SLOTEFLIX_AGENT_TOKEN" "$SLOTEFLIX_AGENT_SECRET" | base64)
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/games/get-clone-status" \
-H "Authorization: Bearer $TOKEN"
// 200
{ "status": true, "is_cloned": 1 }
// 400 — Bearer ausente
{ "status": false, "error": "INVALID_TOKEN" }
// 400 — Base64 decodificado não tem o formato "token:secret"
{ "status": false, "error": "INVALID_TOKEN2" }
// 400 — par não corresponde a nenhum agente
{ "status": false, "error": "INVALID_AGENT" }
Alternar flag de clone
Mesma autenticação do anterior. Inverte o valor atual e retorna o novo — não aceita um valor alvo.
{ "status": true, "is_cloned": false }
get-clone-status antes e depois. Para
definir um valor explícito, use agents/toggle-status,
que aceita o campo status.
Free spins
Concede e cancela rodadas grátis. Disponível para jogos com has_freespins = 1, servidos pelo
distribuidor que suporta o recurso.
Criar free spins
| Campo | Tipo | Descrição | |
|---|---|---|---|
agent_code | OBRIG. | string | Seu código de agente. |
agent_token | OBRIG. | string | Seu token. |
user_id | OBRIG. | string|int | Seu ID de jogador. |
game_id | OBRIG. | string | O game_code do catálogo. |
betlevel | OBRIG. | int | Nível de aposta de cada rodada grátis. |
freespincount | OBRIG. | int | Quantidade de rodadas. |
expiretime | OBRIG. | string | Momento de expiração da concessão. |
freespinID | OBRIG. | string | Seu identificador. Guarde — é o que permite cancelar depois. |
rtp | opcional | int | RTP alvo. |
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/games/freespin" \
-H "Content-Type: application/json" \
-d '{
"agent_code": "meu_agente",
"agent_token": "'"$SLOTEFLIX_AGENT_TOKEN"'",
"user_id": "1042",
"game_id": "vs20olympgate",
"betlevel": 1,
"freespincount": 20,
"expiretime": "2026-08-30 23:59:59",
"freespinID": "promo-2026-08-1042"
}'
// 200 — o conteúdo de "status" vem do distribuidor
{ "status": true }
// 400
{ "error": "Agent not found" }
{ "error": "Invalid agent token" }
{ "error": "Game not found or inactive" }
{ "error": "No active distributor found for this game" }
Cancelar free spins
Cancela uma concessão. Exige agent_code, agent_token, user_id,
game_id e o mesmo freespinID usado na criação. rtp é opcional.
transaction com
type: "win", exatamente como um prêmio normal. Não existe callback específico de free spin —
se você quiser separar bônus de dinheiro real na contabilidade, correlacione pelo round_id
das rodadas concedidas.
Seamless Wallet — visão geral
Esta é a parte que você implementa. Um único endpoint recebe todos os eventos financeiros,
roteados pelo campo method do corpo JSON.
method | Quando dispara | Você precisa | Obrigatório |
|---|---|---|---|
account_details |
Abertura da sessão de jogo | Devolver dados + saldo do jogador | sim |
user_balance |
Jogo consulta saldo | Devolver o saldo atual | sim |
transaction · type:"bet" |
Jogador aposta | Debitar e devolver o novo saldo | sim |
transaction · type:"win" |
Rodada premiada | Creditar e devolver o novo saldo | sim |
refund |
Rodada cancelada / descartada | Estornar a aposta | sim |
rollback |
Reversão de crédito já pago | Retirar o valor creditado | recomendado |
Contrato base
Campos presentes em todo callback
| Campo | Tipo | Descrição |
|---|---|---|
method | string | Roteia a operação. Sempre presente. |
agent_code | string | Seu código de agente. |
agent_token | string | Seu token — valide. |
agent_secret_key | string | Sua secret — valide. |
A autenticação do seamless é o trio agent_code + agent_token +
agent_secret_key no corpo. Não há assinatura HMAC nem header de autenticação
nesses callbacks.
Portanto, na sua ponta: (1) compare os três valores em tempo constante contra o que você tem guardado, (2) rejeite se qualquer um divergir, (3) exija HTTPS, e (4) restrinja por IP de origem junto ao time de integração. Sem essa validação, qualquer um que descubra a URL debita da sua carteira.
Formato da resposta
Responda sempre HTTP 200 com Content-Type: application/json:
// Sucesso
{
"status": 1,
"balance": 1234.56
}
// Falha — use o código apropriado da tabela de status
{
"status": 0,
"error": "Insufficient funds"
}
| Campo | Tipo | Descrição | |
|---|---|---|---|
status | OBRIG. | int | 1 = sucesso. Qualquer outro valor é erro — veja a tabela. |
balance | OBRIG. | number | Saldo do jogador depois da operação, 2 casas decimais. |
error | opcional | string | Mensagem legível. Ajuda no diagnóstico, não altera comportamento. |
Sempre inclua balance, mesmo em erro. Vários caminhos do agregador
testam a presença desse campo antes de olhar o status. Uma resposta sem
balance pode ser tratada como falha de infraestrutura em vez do erro específico que você
quis sinalizar.
balance deve ser numérico — 1234.56, não "1234.56".
status: 15 (Timeout) e rejeita a rodada.
Garanta que /api/sloteflix_api esteja fora de qualquer middleware de
autenticação de sessão, CSRF ou maintenance mode.
method: account_details
Primeiro callback do ciclo de vida, disparado quando o jogo autentica a sessão. Se falhar, o jogo não abre.
{
"method": "account_details",
"game": "vs20olympgate",
"agent_code": "meu_agente",
"agent_token": "3f8a…",
"agent_secret_key": "9b1c…",
"user_id": "1042"
}
{
"status": 1,
"message": "SUCCESS",
"balance": 1500.00,
"user_id": "1042",
"email": "jogador@example.com",
"date": "2024-11-02 14:31:07"
}
| Campo da resposta | Descrição | |
|---|---|---|
status | OBRIG. | 1 em sucesso. |
balance | OBRIG. | Saldo atual. Sem ele o launch é abortado. |
user_id | recomendado | Ecoe o valor recebido. Alguns distribuidores exigem para amarrar a sessão. |
email, date, message | opcional | Metadados de conveniência. |
status: 11 (Player is blocked) ou
status: 10 (Limits reached) — a sessão não é criada e o jogador nunca chega a apostar.
method: user_balance
O callback mais frequente da integração — o jogo consulta saldo a cada atualização de tela. Otimize este caminho acima de todos os outros.
{
"method": "user_balance",
"game": "vs20olympgate",
"agent_code": "meu_agente",
"agent_token": "3f8a…",
"agent_secret_key": "9b1c…",
"user_id": "1042"
}
{ "status": 1, "balance": 1498.50 }
balance aqui é convertida em OP_31 · Insufficient funds para
o provedor — o jogador vê "saldo insuficiente" mesmo tendo dinheiro. Se a leitura falhar de verdade,
prefira devolver status: 5 (Internal error) com um balance.
Existe uma variante method: "balance" no código do agregador com payload
praticamente idêntico (sem o campo game, com distributor).
Ela está dormente — nenhum caminho de produção a dispara hoje.
Ainda assim, trate "balance" como sinônimo de "user_balance" no seu
roteador. Custa uma linha e evita um incidente se a variante for ativada.
method: transaction — aposta
Debita a aposta. É a operação mais crítica: se você responder erro, a rodada é rejeitada e o jogador recebe uma mensagem do provedor.
{
"method": "transaction",
"type": "bet",
"session_id": "eyJpdiI6IkQr…",
"game": "vs20olympgate",
"agent_code": "meu_agente",
"agent_token": "3f8a…",
"agent_secret_key": "9b1c…",
"round_id": "timeless-88213004",
"transaction_id": "timeless-99551277",
"bet": 1.50,
"win": 0,
"user_id": "1042",
"distributor": "timeless"
}
| Campo | Tipo | Descrição |
|---|---|---|
type | "bet" | Discrimina a operação dentro de transaction. |
transaction_id | string | Chave de idempotência. Único globalmente. |
round_id | string | Agrupa aposta e prêmio da mesma rodada. |
bet | number | Valor a debitar, positivo, 2 casas. |
win | number | Sempre 0 em aposta. |
session_id | string | Sessão de jogo. Útil para auditoria. |
distributor | string | Origem da transação. |
// Debitado com sucesso
{ "status": 1, "balance": 1497.00 }
// Saldo insuficiente — a rodada é rejeitada
{ "status": 0, "balance": 0.80, "error": "Insufficient funds" }
// Já processada — devolva sucesso com o saldo atual (idempotência)
{ "status": 1, "balance": 1497.00 }
Debite atomicamente. A verificação de saldo e a escrita precisam estar na mesma
transação de banco, com SELECT … FOR UPDATE na carteira (ou UPDATE … WHERE balance >= :bet
checando as linhas afetadas). Um jogador com duas abas abertas gera apostas concorrentes.
Grave o transaction_id com índice único na mesma transação. Isso resolve
idempotência e corrida de uma vez.
method: transaction — prêmio
Credita o prêmio. Aparece também no estorno de rodada, quando o distribuidor devolve valor como crédito.
{
"method": "transaction",
"type": "win",
"session_id": "eyJpdiI6IkQr…",
"game": "vs20olympgate",
"agent_code": "meu_agente",
"agent_token": "3f8a…",
"agent_secret_key": "9b1c…",
"transaction_id": "timeless-99551301",
"transaction_id_old": "timeless-88213004",
"round_id": "timeless-88213004",
"bet": 0,
"win": 45.00,
"user_id": "1042",
"distributor": "timeless"
}
| Campo | Tipo | Descrição |
|---|---|---|
type | "win" | Operação de crédito. |
transaction_id | string | Chave de idempotência do crédito. Diferente do transaction_id da aposta. |
transaction_id_old | string | Aponta para a rodada de origem. Presente em créditos, ausente em apostas. |
win | number | Valor a creditar, 2 casas. |
bet | number | Sempre 0 em prêmio. |
{ "status": 1, "balance": 1542.00 }
Nunca rejeite um prêmio por falta de saldo. Crédito só aumenta saldo. Se você responder erro aqui, o jogador ganhou e não recebeu — e a reconciliação vira trabalho manual.
Numa falha real de infraestrutura, responda status: 5 e alerte on-call.
As 3 tentativas do agregador dão margem para uma indisponibilidade curta.
win. Não espere um crédito de
valor zero para fechar a rodada — use round_id para correlacionar.
method: refund
Estorna uma aposta: rodada cancelada pelo provedor, aposta descartada, ou falha depois do débito. Devolva o valor da aposta ao jogador.
{
"method": "refund",
"agent_code": "meu_agente",
"agent_token": "3f8a…",
"agent_secret_key": "9b1c…",
"transaction_id": "timeless-99551277",
"user_id": "1042",
"distributor": "timeless",
"balance": true
}
| Campo | Tipo | Descrição |
|---|---|---|
transaction_id | string | Aponta para a transação original que você deve estornar. Não é um ID novo. |
user_id | string|int | Jogador. Ausente em alguns distribuidores — resolva pelo transaction_id quando faltar. |
balance | bool | Quando true, o agregador vai ler balance da sua resposta. Ausente em alguns distribuidores. |
distributor | string | Origem. |
refund varia por distribuidor. Alguns enviam apenas
agent_code, agent_token, agent_secret_key,
transaction_id e distributor. Sua implementação precisa achar o jogador
a partir do transaction_id e não depender de user_id.
// Estornado
{ "status": 1, "balance": 1498.50 }
// Já estornado — devolva sucesso, não erro (idempotência)
{ "status": 1, "balance": 1498.50 }
// transaction_id desconhecido
{ "status": 14, "balance": 1498.50, "error": "Transaction not found" }
status: 1 ou status: 0 como "estorno
reconhecido", mas outros exigem estritamente status: 1.
Sempre devolva 1 quando o estorno estiver efetivado — inclusive quando
já estava efetivado antes.
method: rollback
O inverso do refund: retira do jogador um valor que já havia sido creditado.
Usado quando o provedor reverte um prêmio pago.
{
"method": "rollback",
"agent_code": "meu_agente",
"agent_token": "3f8a…",
"agent_secret_key": "9b1c…",
"user_id": "1042",
"transaction_id": "betby-77120043",
"amount": 45.00,
"distributor": "betby"
}
| Campo | Tipo | Descrição |
|---|---|---|
amount | number | Valor a retirar, positivo. Note: amount, não bet/win. |
transaction_id | string | Identifica a reversão. Use como chave de idempotência. |
user_id | string|int | Jogador de quem retirar. |
{ "status": 1, "balance": 1497.00 }
O agregador exige estritamente status: 1 aqui — qualquer outro valor é tratado como
erro de rollback.
Decida a política de saldo negativo antes do go-live. Se o jogador já sacou o
prêmio revertido, retirar leva a saldo negativo. As opções são permitir negativo, registrar como
débito pendente, ou zerar e abrir um caso — mas responda status: 1 em todas elas,
senão o agregador reprocessa.
Códigos de status do seamless
O status que você devolve é traduzido para o código de erro que o provedor mostra ao jogador.
Escolher o código certo é o que faz a mensagem na tela ter sentido.
| Você envia | Vira | Significado | Quando usar |
|---|---|---|---|
1 sucesso | — | Operação concluída | Sempre que der certo, inclusive em repetição idempotente. |
0 | OP_31 | Insufficient funds | Saldo menor que a aposta. Nunca em prêmio. |
2 | OP_20 | Invalid hash | Assinatura ou formato inválido. |
4 | OP_34 | Player not found | user_id não existe na sua base. |
5 | OP_50 | Internal error | Falha do seu lado. Prefira este a devolver JSON inválido. |
6 | OP_50 | Unknown error | Erro não classificado. |
7 | OP_21 | Invalid token | Trio de credenciais não confere. |
8 | OP_22 | Authorization failed | Launch não pode ser autorizado. |
9 | OP_30 | Invalid currency | Moeda da transação ≠ moeda do jogador. |
10 | OP_32 | Limits reached | Limite de jogo responsável atingido. |
11 | OP_33 | Player is blocked | Conta suspensa ou autoexcluída. |
12 | OP_35 | Game is disabled | Jogo desabilitado na sua operação. |
13 | OP_40 | Duplicated transaction ID | Use com cuidado — veja o aviso abaixo. |
14 | OP_41 | Transaction not found | Refund/rollback de transaction_id desconhecido. |
15 | OP_47 | Timeout | Reservado — é o que a Sloteflix assume quando você não responde. |
16 | OP_49 | Operation failed | Operação recusada por regra de negócio. |
17 | OP_50 | Internal error | Erro interno não coberto acima. |
Não use status: 13 para transação repetida. É contraintuitivo, mas é o
comportamento correto: quando o mesmo transaction_id chega de novo, a operação
já está efetivada — devolva status: 1 com o saldo atual.
Responder 13 faz o provedor tratar como falha e a rodada pode ser cancelada mesmo já
tendo sido debitada. Reserve 13 para colisão real de ID entre transações
semanticamente diferentes.
status fora desta tabela cai no padrão OP_50 · Internal error.
Nunca invente códigos.
Idempotência & reconciliação
Redes falham, timeouts acontecem, e a Sloteflix tenta 3 vezes com o mesmo
transaction_id. Idempotência não é otimização aqui — é requisito.
A chave é o transaction_id
Ele já vem prefixado pelo distribuidor (timeless-, betby-, …), então é único
no escopo global. Guarde exatamente como recebeu — não remova o prefixo.
CREATE TABLE sloteflix_transactions ( id BIGSERIAL PRIMARY KEY, transaction_id VARCHAR(191) NOT NULL, round_id VARCHAR(191), player_id BIGINT NOT NULL, method VARCHAR(32) NOT NULL, -- transaction | refund | rollback type VARCHAR(16), -- bet | win amount DECIMAL(20,2) NOT NULL, balance_after DECIMAL(20,2) NOT NULL, distributor VARCHAR(32), game VARCHAR(191), session_id VARCHAR(191), refunded_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT uq_sloteflix_tx UNIQUE (transaction_id) ); CREATE INDEX idx_sloteflix_player_round ON sloteflix_transactions (player_id, round_id);
public function bet(array $payload): array
{
return DB::transaction(function () use ($payload) {
// 1) idempotência: já vi esse transaction_id?
$existing = SloteflixTransaction::where('transaction_id', $payload['transaction_id'])->first();
if ($existing) {
return ['status' => 1, 'balance' => (float) $existing->balance_after];
}
// 2) trava a carteira — jogador com duas abas gera apostas concorrentes
$wallet = Wallet::where('user_id', $payload['user_id'])->lockForUpdate()->first();
if (! $wallet) {
return ['status' => 4, 'balance' => 0, 'error' => 'Player not found'];
}
$bet = round((float) $payload['bet'], 2);
if (bccomp((string) $wallet->balance, (string) $bet, 2) < 0) {
return ['status' => 0, 'balance' => (float) $wallet->balance, 'error' => 'Insufficient funds'];
}
// 3) debita e registra na MESMA transação
$wallet->decrement('balance', $bet);
$wallet->refresh();
SloteflixTransaction::create([
'transaction_id' => $payload['transaction_id'],
'round_id' => $payload['round_id'] ?? null,
'player_id' => $payload['user_id'],
'method' => 'transaction',
'type' => 'bet',
'amount' => $bet,
'balance_after' => $wallet->balance,
'distributor' => $payload['distributor'] ?? null,
'game' => $payload['game'] ?? null,
'session_id' => $payload['session_id'] ?? null,
]);
return ['status' => 1, 'balance' => (float) $wallet->balance];
}, attempts: 3);
}
first() antes de qualquer INSERT. É o índice único que
garante a integridade — capture a violação de unicidade, releia a transação existente e devolva
status: 1 com o saldo dela.
Reconciliação
Use POST agents/consult para comparar seus totais com os da
Sloteflix por provedor e período. Rode diariamente.
- Sua soma de apostas > a nossa — você debitou rodadas que não foram aceitas. Verifique se está debitando antes de receber o callback.
- Sua soma de apostas < a nossa — callbacks perdidos. Investigue timeouts e respostas não-JSON.
- Prêmios divergentes — normalmente
winnão processado por erro seu. Cheque logs destatus: 5.
X-Attempt — com retenção de pelo menos 90 dias. Sem esse log, disputa financeira com
provedor não se resolve.
Servidor de exemplo
Esqueleto completo do roteador seamless. Adapte a persistência à sua carteira.
// routes/api.php
// IMPORTANTE: fora dos middlewares de sessão/CSRF, e fora do maintenance mode.
Route::post('/sloteflix_api', [SloteflixWalletController::class, 'handle'])
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
class SloteflixWalletController extends Controller
{
public function handle(Request $request)
{
$payload = $request->all();
// 1) autentica o trio de credenciais em tempo constante
if (! $this->validCredentials($payload)) {
return response()->json(['status' => 7, 'balance' => 0, 'error' => 'Invalid token']);
}
// 2) roteia por method (+ type quando for transaction)
$key = $payload['method'] ?? '';
if ($key === 'transaction') {
$key .= ':' . ($payload['type'] ?? '');
}
try {
return response()->json(match ($key) {
'account_details' => $this->wallet->accountDetails($payload),
'user_balance',
'balance' => $this->wallet->balance($payload),
'transaction:bet' => $this->wallet->bet($payload),
'transaction:win' => $this->wallet->win($payload),
'refund' => $this->wallet->refund($payload),
'rollback' => $this->wallet->rollback($payload),
default => ['status' => 6, 'balance' => 0, 'error' => "Unknown method: {$key}"],
});
} catch (\Throwable $e) {
// NUNCA deixe uma exceção virar HTML — sempre JSON com status numérico
report($e);
return response()->json(['status' => 5, 'balance' => 0, 'error' => 'Internal error']);
}
}
private function validCredentials(array $p): bool
{
$agent = config('sloteflix.agents.' . ($p['agent_code'] ?? ''));
if (! $agent) {
return false;
}
return hash_equals($agent['token'], (string) ($p['agent_token'] ?? ''))
&& hash_equals($agent['secret'], (string) ($p['agent_secret_key'] ?? ''));
}
}
import express from 'express';
import crypto from 'node:crypto';
const app = express();
app.use(express.json({ limit: '256kb' }));
const AGENTS = {
[process.env.SLOTEFLIX_AGENT_CODE]: {
token: process.env.SLOTEFLIX_AGENT_TOKEN,
secret: process.env.SLOTEFLIX_AGENT_SECRET,
},
};
const safeEqual = (a = '', b = '') => {
const ba = Buffer.from(String(a));
const bb = Buffer.from(String(b));
return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
};
app.post('/api/sloteflix_api', async (req, res) => {
const p = req.body ?? {};
const agent = AGENTS[p.agent_code];
if (!agent || !safeEqual(agent.token, p.agent_token) || !safeEqual(agent.secret, p.agent_secret_key)) {
return res.json({ status: 7, balance: 0, error: 'Invalid token' });
}
const key = p.method === 'transaction' ? `transaction:${p.type}` : p.method;
try {
switch (key) {
case 'account_details': return res.json(await wallet.accountDetails(p));
case 'user_balance':
case 'balance': return res.json(await wallet.balance(p));
case 'transaction:bet': return res.json(await wallet.bet(p));
case 'transaction:win': return res.json(await wallet.win(p));
case 'refund': return res.json(await wallet.refund(p));
case 'rollback': return res.json(await wallet.rollback(p));
default:
return res.json({ status: 6, balance: 0, error: `Unknown method: ${key}` });
}
} catch (err) {
req.log?.error({ err, payload: p }, 'seamless handler failed');
return res.json({ status: 5, balance: 0, error: 'Internal error' });
}
});
// Handler de erro final: garante JSON mesmo em body malformado
app.use((err, _req, res, _next) => res.json({ status: 5, balance: 0, error: 'Internal error' }));
import hmac, os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
AGENTS = {
os.environ["SLOTEFLIX_AGENT_CODE"]: {
"token": os.environ["SLOTEFLIX_AGENT_TOKEN"],
"secret": os.environ["SLOTEFLIX_AGENT_SECRET"],
}
}
def valid(p: dict) -> bool:
agent = AGENTS.get(p.get("agent_code", ""))
if not agent:
return False
return (hmac.compare_digest(agent["token"], str(p.get("agent_token", "")))
and hmac.compare_digest(agent["secret"], str(p.get("agent_secret_key", ""))))
@app.post("/api/sloteflix_api")
async def sloteflix_api(request: Request):
p = await request.json()
if not valid(p):
return JSONResponse({"status": 7, "balance": 0, "error": "Invalid token"})
key = f"transaction:{p.get('type')}" if p.get("method") == "transaction" else p.get("method")
handlers = {
"account_details": wallet.account_details,
"user_balance": wallet.balance,
"balance": wallet.balance,
"transaction:bet": wallet.bet,
"transaction:win": wallet.win,
"refund": wallet.refund,
"rollback": wallet.rollback,
}
handler = handlers.get(key)
if handler is None:
return JSONResponse({"status": 6, "balance": 0, "error": f"Unknown method: {key}"})
try:
return JSONResponse(await handler(p))
except Exception:
logger.exception("seamless handler failed", extra={"payload": p})
return JSONResponse({"status": 5, "balance": 0, "error": "Internal error"})
Note o padrão: nas três linguagens, toda saída é JSON com
status numérico e balance. Nenhum caminho — nem o de exceção — produz HTML ou
status HTTP diferente de 200. É isso que mantém a integração previsível.
Testando localmente
Reproduza um callback contra o seu handler e cheque a latência:
curl -s -o /dev/null -w 'HTTP %{http_code} · %{time_total}s\n' \
-X POST "https://minhaoperacao.com/api/sloteflix_api" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-Attempt: 1" \
-d '{
"method": "transaction",
"type": "bet",
"session_id": "test-session",
"game": "vs20olympgate",
"agent_code": "meu_agente",
"agent_token": "'"$SLOTEFLIX_AGENT_TOKEN"'",
"agent_secret_key": "'"$SLOTEFLIX_AGENT_SECRET"'",
"round_id": "test-round-1",
"transaction_id": "test-tx-1",
"bet": 1.50,
"win": 0,
"user_id": "1042",
"distributor": "timeless"
}'
# time_total tem que ficar bem abaixo de 0.600s.
# Rode de novo com o MESMO transaction_id: o saldo não pode mudar (idempotência).
Agents API
Endpoints administrativos para provisionar e gerenciar agentes. Úteis para quem revende a integração ou opera múltiplas marcas.
Quatro destes cinco endpoints são protegidos por um token administrativo mestre,
enviado no campo token do corpo. Ele é referenciado aqui como
{SLOTEFLIX_MASTER_TOKEN} e vale para toda a plataforma — não é por agente.
Se você é um operador integrando a Sloteflix, você não recebe esse token. Use
apenas get-agent-data, que autentica com as suas próprias
credenciais.
Criar agente
| Campo | Tipo | Descrição | |
|---|---|---|---|
token | OBRIG. | string | {SLOTEFLIX_MASTER_TOKEN}. |
email | OBRIG. | E-mail de login do usuário do agente. | |
password | OBRIG. | string | Mínimo 6 caracteres. |
subdomain | OBRIG. | string | Vira o agent_code, o nome do usuário e a base do endpoint. |
{
"id": 87,
"message": "Agente criado com sucesso",
"data": {
"agent_sponsor_id": 1,
"agent_id": 412,
"is_cloned": 1,
"agent_code": "minhaoperacao",
"agent_token": "Xk9pQ2mL7vRt4nZa8YbW3cJhF6sDgE1u",
"agent_secret_key": "Tq5wH8jN2bV6xM1oPzL4rK7yA3cS9dFg",
"agent_type": "default",
"agent_status": 1,
"api_type": "seamless",
"ggr": 20,
"ggr_cloned": 20,
"balance": 200000000,
"endpoint": "https://minhaoperacao.lumosgaming.com",
"id": 87
}
}
Valores atribuídos na criação
| Campo | Valor | Significado |
|---|---|---|
agent_token | aleatório, 32 chars | Credencial de jogo. |
agent_secret_key | aleatório, 32 chars | Credencial de autenticação JWT. |
api_type | seamless | Único modo suportado. |
agent_status | 1 | Ativo. |
is_cloned | 1 | Trilha de clone habilitada por padrão. |
ggr / ggr_cloned | 20 | Percentual de GGR do acordo comercial. |
balance | 200000000 | Crédito interno do agente junto ao agregador — não saldo de jogador. |
endpoint | https://{subdomain}.lumosgaming.com | Placeholder. Troque pela URL real antes do go-live. |
O endpoint gerado na criação é um placeholder derivado do
subdomain. Enquanto ele não apontar para a sua plataforma real, todos os callbacks
seamless vão para o lugar errado e nenhuma aposta é liquidada.
Ajustar o endpoint é o primeiro passo depois da criação. Veja
Painel de configuração.
// 200 — e-mail já tem agente: devolve o existente, não cria outro
{ "message": "Agente já existente", "data": { "...": "..." } }
// 400 — e-mail existe como usuário, mas sem agente vinculado
{ "message": "Usuário existente, mas sem agente associado." }
// 400 — erro de validação
{ "email": ["The email field must be a valid email address."] }
// 403 — token mestre inválido
{ "message": "Acesso não autorizado. Token inválido." }
Relatório GGR
Totais de aposta e prêmio por provedor num intervalo de datas. É a base da reconciliação.
| Campo | Tipo | Descrição | |
|---|---|---|---|
token | OBRIG. | string | {SLOTEFLIX_MASTER_TOKEN}. |
agent_code | OBRIG. | string | Agente a consultar. |
start_date | OBRIG. | Y-m-d | Início, inclusivo. |
end_date | OBRIG. | Y-m-d | Fim, inclusivo. |
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/agents/consult" \
-H "Content-Type: application/json" \
-d '{
"token": "'"$SLOTEFLIX_MASTER_TOKEN"'",
"agent_code": "minhaoperacao",
"start_date": "2026-07-01",
"end_date": "2026-07-31"
}'
// 200
{
"data": [
{ "provider": "pragmatic-play", "total_bet": "184320.50", "total_win": "171004.25" },
{ "provider": "pgsoft", "total_bet": "92110.00", "total_win": "88740.10" }
]
}
// 404 — agente inexistente
{ "message": "Agente não encontrado." }
// 404 — nenhum dado no período (note: 404, não 200 com lista vazia)
{ "message": "Nenhum dado encontrado para o período especificado." }
// 400 — formato de data inválido
{ "start_date": ["The start date does not match the format Y-m-d."] }
// 403 — token mestre inválido
{ "message": "Acesso não autorizado. Token inválido." }
200 com array vazio.
Trate 404 como "zero transações", não como erro.
is_affiliate = 0) e agrupa por
provedor. GGR por provedor = total_bet - total_win; a sua comissão é esse valor
multiplicado pelo ggr do agente.
Dados do agente
O único endpoint de agentes que você pode chamar com as suas próprias credenciais.
Serve como health check: confirma que agent_code e agent_token estão válidos.
| Campo | Tipo | Descrição | |
|---|---|---|---|
agent_code | OBRIG. | string | Seu código de agente. |
agent_token | OBRIG. | string | Seu token. |
curl -s -X POST "https://{SLOTEFLIX_HOST}/api/v1/agents/get-agent-data" \
-H "Content-Type: application/json" \
-d '{"agent_code":"minhaoperacao","agent_token":"'"$SLOTEFLIX_AGENT_TOKEN"'"}'
// 200
{ "id": 87, "agent_code": "minhaoperacao" }
// 400
{ "error": "Agent not found" }
{ "error": "Invalid agent token" }
Consultar status
Retorna a flag de jogos clone do agente. Requer token mestre e
agent_code.
{
"message": "Status consultado com sucesso",
"data": { "id": 87, "agent_code": "minhaoperacao", "status": true }
}
status aqui reflete is_cloned — não
o agent_status (ativo/inativo).
Alternar status
| Campo | Tipo | Descrição | |
|---|---|---|---|
token | OBRIG. | string | {SLOTEFLIX_MASTER_TOKEN}. |
agent_code | OBRIG. | string | Agente alvo. |
status | opcional | bool | Define o valor. Se omitido, inverte o atual. |
{
"message": "Status ativado com sucesso para o agente minhaoperacao",
"data": { "id": 87, "agent_code": "minhaoperacao", "status": true }
}
status explicitamente em automação. Omitir transforma a
chamada num toggle, e um retry desfaz a mudança. Este é o endpoint preferível a
change-clone-status, que só faz toggle.
Painel de configuração
As credenciais e o endpoint do seamless são gerenciados no painel administrativo da Sloteflix.
Campos disponíveis no cadastro de agente:
| Campo | Rótulo no painel | Notas |
|---|---|---|
agent_code | Agente Código | Identificador da operação. |
agent_token | Agente Token | Somente leitura — gerado automaticamente. |
agent_secret_key | Agente Secret | Somente leitura — gerado automaticamente. |
endpoint | Site EndPoint | É aqui que você configura o destino do seamless. URL sem barra final. |
api_type | Tipo de API | seamless. |
agent_type | Tipo de Agente | reseller permite criar subcontas. |
ggr | GGR | Percentual do acordo comercial. |
ggr_cloned | GGR Jogos Clones | Percentual da trilha de clone. |
balance | Saldo | Crédito do agente junto ao agregador. |
agent_status | Status | Ativa/desativa o agente. |
is_cloned | Jogos Clones | Habilita a trilha de clone no launch. |
currency | Moeda | Padrão BRL. |
Agentes são cacheados indefinidamente. O registro fica em cache sob as chaves
agent_{id} e agent_{agent_code}_{agent_token}, gravadas com
forever — sem TTL.
Consequência prática: mudar o endpoint direto no banco não tem efeito
até o cache ser invalidado. Faça a alteração pelo painel ou por um fluxo que limpe as duas chaves.
Depois de alterar, confirme com uma aposta de teste que o callback chegou na URL nova.
Códigos de erro
API Sloteflix
Erros de código fixo usam o formato {"status": false, "error": "CODE"} com HTTP 400:
| Código | Endpoints | Significado & correção |
|---|---|---|
INVALID_TOKEN | authentication, clone status | Header Authorization ausente ou vazio. Envie Bearer base64("token:secret"). |
INVALID_TOKEN2 | clone status | Base64 decodificou, mas não tem o formato token:secret. Verifique se há exatamente um dois-pontos. |
INVALID_AGENT | authentication, clone status | O par token/secret não corresponde a nenhum agente. Confira as credenciais. |
INVALID_GAME | game_launch | Jogo inexistente, inativo, ou nenhum distribuidor conseguiu abrir. Cheque o game_code em games-status. |
Erros de mensagem livre usam {"error": "mensagem"}, também com HTTP 400:
| Mensagem | Causa |
|---|---|
Agent not found | agent_code não existe. |
Invalid agent token | agent_token não corresponde ao agent_code. |
Invalid user ID | user_id vazio ou 0. |
Game not found or inactive | game_code inexistente ou com status ≠ 1. |
No active distributor found for this game | Nenhum distribuidor ativo serve esse jogo. |
Erros de validação seguem o formato padrão do Laravel — mapa de campo para lista de mensagens, HTTP 400:
{
"game_id": ["The game id field is required."],
"currency": ["The currency field is required."]
}
2xx + payload esperado presente = sucesso;
qualquer outra coisa = falha, e logue o corpo cru.
Seamless
Do seu lado, use os códigos de status. Do lado da Sloteflix,
status: 15 é sintetizado quando você não responde JSON válido em 3 tentativas:
{
"status": 15,
"error": "Falha após 3 tentativas: resposta não é JSON válido"
}
Ver isso nos logs significa uma destas quatro coisas: seu handler passou de 600 ms, devolveu HTML, devolveu corpo vazio, ou a conexão não estabeleceu em 100 ms.
Troubleshooting
Sintomas reais de integração, e o que costuma estar por trás deles.
O jogo não abre
| Sintoma | Causa provável | O que fazer |
|---|---|---|
INVALID_GAME em todos os jogos |
Você está passando o id numérico em vez do game_code. |
Use o game_code de games-status. |
INVALID_GAME em alguns jogos só |
Distribuidor indisponível, ou o jogo perdeu o distribuidor ativo. | Confirme o código em games-status. Se estiver lá, é indisponibilidade momentânea — não remova o jogo do catálogo. |
Erro de validação em demo_agent |
Você omitiu o parâmetro por ele valer 0. |
Envie demo_agent=0 explicitamente. É obrigatório. |
Invalid user ID |
user_id vazio, null ou 0. |
Envie um ID de jogador real, como string. |
| Launch responde, mas o jogo mostra erro na tela | O account_details falhou no seu endpoint. |
Cheque seus logs de /api/sloteflix_api. É onde o problema está, não no launch. |
| Jogo abre com saldo zerado | account_details respondeu sem balance, ou com balance como string. |
Devolva balance numérico, sempre. |
Apostas falhando
| Sintoma | Causa provável | O que fazer |
|---|---|---|
| Nenhum callback chega no seu servidor | O endpoint do agente está errado — provavelmente ainda no placeholder {subdomain}.lumosgaming.com. |
Corrija o endpoint e garanta a invalidação do cache — veja Painel. |
Callbacks pararam depois de mudar o endpoint |
Cache do agente não foi invalidado — as chaves são gravadas com forever. |
Limpe agent_{id} e agent_{code}_{token}, e confirme com uma aposta de teste. |
status: 15 nos logs do agregador |
Timeout, HTML na resposta, corpo vazio, ou conexão acima de 100 ms. | Meça a latência do handler. Confirme que toda saída é JSON, inclusive no caminho de exceção. |
| Jogador vê "saldo insuficiente" tendo saldo | Sua resposta de user_balance veio sem balance — vira OP_31. |
Inclua balance numérico em toda resposta, inclusive nas de erro. |
| Débito duplo na conta do jogador | Sem idempotência: você processou a 2ª ou 3ª tentativa do mesmo transaction_id. |
Índice único em transaction_id; repetição devolve status: 1 com o saldo já gravado. |
| Rodada cancelada apesar do débito ter dado certo | Você devolveu status: 13 numa tentativa repetida. |
Transação repetida = status: 1. Reserve 13 para colisão real de ID. |
| Saldo do jogador negativo do nada | rollback de um prêmio que já havia sido sacado. |
Comportamento esperado do fluxo. Defina a política antes do go-live — mas continue respondendo status: 1. |
| Latência boa em teste, ruim em produção | Cold start de serverless, ou consulta de saldo sem índice sob carga. | Provisioned concurrency ou servidor sempre quente. Indexe a carteira por user_id. |
Falhas intermitentes, sempre no X-Attempt: 1 |
Handshake TLS acima de 100 ms — o retry pega a conexão já quente. | Habilite keep-alive e session resumption. Corte proxies extras do caminho. |
Divergência de valores
| Sintoma | Causa provável | O que fazer |
|---|---|---|
| Valores 100× maiores ou menores | Você tratou os valores como centavos. | São decimais na moeda do jogador. 1.50 é R$ 1,50 — veja Convenções. |
| Centavos somem na terceira casa | Arredondamento inconsistente. | Arredonde para 2 casas com half-up, e use aritmética decimal — não float. |
| Sua soma de apostas > a nossa | Você debita antes de receber o callback. | Só debite dentro do handler do seamless. |
| Sua soma de apostas < a nossa | Callbacks perdidos. | Busque status: 15 e respostas não-JSON nos logs. |
consult devolve 404 |
Zero transações no período — não é erro. | Trate 404 como resultado vazio. |
Autenticação
| Sintoma | Causa provável | O que fazer |
|---|---|---|
INVALID_TOKEN na autenticação |
Header ausente, ou você mandou o par sem Base64. | Bearer base64("token:secret") — Base64 simples, não Basic Auth. |
INVALID_TOKEN2 |
O Base64 decodificou, mas não tem exatamente um dois-pontos. | Verifique se não há espaço, quebra de linha ou dois-pontos extra na string. |
INVALID_AGENT |
O par token/secret não corresponde a nenhum agente. | Valide as credenciais com get-agent-data. |
401 em games/provider |
JWT expirado ou ausente. | Renove em authentication. Faça cache do JWT, mas respeite o TTL. |
transaction_id e o payload cru, o diagnóstico
vira adivinhação.
Checklist de go-live
Configuração
endpointaponta para a sua URL de produção, sem barra final e sem path.- Cache do agente invalidado depois da última alteração de
endpoint. currencydo agente igual à moeda das carteiras dos jogadores.- Credenciais em secret manager — não em repositório, não em build de front-end.
agent_statusativo eis_clonedno valor acordado.
Seamless — corretude
account_details,user_balance,transaction:bet,transaction:winerefundimplementados.rollbackimplementado, com política de saldo negativo definida.balancepresente em toda resposta, inclusive de erro, como número.- Índice único em
transaction_id; repetição devolvestatus: 1com o saldo atual. - Débito atômico com lock de linha — testado com apostas concorrentes.
refundresolve o jogador pelotransaction_id, sem depender deuser_id.winnunca rejeitado por saldo insuficiente.- Valores tratados como decimais de 2 casas na moeda do jogador — não centavos.
- Trio de credenciais validado com comparação em tempo constante.
Seamless — robustez
- p99 do handler abaixo de 600 ms sob carga de produção. Meça, não estime.
- Rota fora de middleware de sessão, CSRF, rate limit e maintenance mode.
- Handler de exceção global devolve
{"status":5,"balance":…}— nunca HTML. - Sem cold start no caminho (evite serverless sem provisioned concurrency).
- HTTPS com certificado válido; handshake rápido (session resumption, keep-alive).
- Allowlist de IP alinhada com o time de integração.
Catálogo
- Job de sincronização com
games/allagendado; upsert porgame_code. - Jogos ausentes marcados como inativos, nunca deletados.
- Artes espelhadas no seu CDN.
- Launch chamado exclusivamente do backend.
Operação
- Log de 90+ dias com payload cru, resposta, latência e
X-Attempt. - Alerta em taxa de
status: 5, latência p99 e volume derefund. - Reconciliação diária via
agents/consult. - Runbook de indisponibilidade do seamless — quem é acionado e o que faz.
Teste de aceitação mínimo antes de abrir para jogadores reais:
- Abrir um jogo e ver o saldo correto na tela.
- Apostar; conferir débito exato e
balanceretornado. - Ganhar; conferir crédito exato.
- Reenviar o mesmo
transaction_idde aposta; o saldo não pode mudar. - Disparar um
refund; conferir que a aposta voltou. - Reenviar o mesmo
refund; o saldo não pode mudar. - Apostar com saldo insuficiente; conferir
status: 0e que nada foi debitado.
OpenAPI & Postman
Variáveis a definir depois de importar a coleção:
| Variável | Exemplo |
|---|---|
base_url | https://{SLOTEFLIX_HOST}/api/v1 |
agent_code | minhaoperacao |
agent_token | seu token de 32 chars |
agent_secret_key | sua secret de 32 chars |
game_code | vs20olympgate |
user_id | 1042 |
Suporte
Ao abrir um ticket, mande sempre estes cinco itens — sem eles o diagnóstico demora:
- Seu
agent_code(nunca o token nem a secret). - O
transaction_idouround_idafetado. - Timestamp com fuso.
- Payload recebido e resposta enviada, crus.
- O que você esperava versus o que aconteceu.