Toki · API
Cobre pelo seu sistema. Que paguem com Toki.
Seu caixa, sua loja online ou seu ERP criam uma cobrança com uma chamada. A Toki devolve um link de pagamento; seu cliente abre, paga pela carteira dele e volta para o seu site. O dinheiro chega à sua conta Toki na hora.
https://api.toki.lat/v1Criar uma credencialComo funciona
- 1. Seu sistema chama
POST /v1/cobroscom o valor. - 2. A Toki responde com uma cobrança e uma
url_checkout. - 3. Você manda seu cliente para lá, ou incorpora na sua própria página.
- 4. Ele paga com o app da Toki e o devolvemos para a sua
url_retorno. - 5. Avisamos por webhook (ou você consulta o status da cobrança).
Sua credencial pede dinheiro; nunca o cobra. Nenhuma chamada a esta API movimenta dinheiro: a cobrança é confirmada pela pessoa no celular dela, da própria sessão e da própria carteira. Se sua chave vazasse, quem a tivesse poderia gerar cobranças em seu nome — chato, e revogável em um clique — mas não tiraria um centavo de ninguém.
Autenticação
Cada chamada leva sua credencial no header Authorization. O token tem a forma <key_id>.<segredo>; você o recebe inteiro ao criar a credencial no seu painel, e só nessa vez.
Authorization: Bearer tk_9f2c8ab1de4057.9d31...c0No painel você também pode restringir de quais IPs a credencial é aceita. Se configurar, uma chamada de outro endereço é recusada mesmo com o segredo certo. É a diferença entre uma chave vazada que funciona de qualquer lugar e uma que não serve fora do seu servidor.
Limite: 120 chamadas por minuto por credencial.
Moeda
A moeda de tudo o que você cobra é a da sua carteira Toki. Não se escolhe por cobrança: um comércio cobra em uma moeda, a dele.
Os valores vão sempre na menor unidade dessa moeda, o padrão de qualquer gateway. O peso chileno não se subdivide, então 15990 são quinze mil novecentos e noventa pesos. Numa moeda com centavos, 1599 são 15,99.
/v1/comercioDiz em que moeda você cobra, o nome do seu comércio e se a credencial é de teste ou de produção. Use para validar sua configuração sem criar uma cobrança de mentira.
{ "comercio": { "nombre": "Minha Loja", "moneda": "CLP", "modo": "prueba" } }Se sua loja cobra em moeda diferente da sua carteira, não integre ainda. A Toki não converte: cobraria o número enviado como se fosse na sua moeda. Fale conosco antes.
Criar uma cobrança
/v1/cobros| Campo | Tipo | O que é |
|---|---|---|
| monto | inteiro, obrigatório | Na menor unidade da sua moeda. Em CLP o peso não se divide: 15990 são quinze mil novecentos e noventa pesos. Numa moeda com centavos, 1599 são 15,99. |
| concepto | texto | O que seu cliente vê. "Nota 4471", "Mesa 12". |
| referencia_externa | texto | Seu identificador. Também torna a cobrança idempotente: repetir com a mesma referência devolve a cobrança que já existe, em vez de criar outra. |
| url_retorno | https | Para onde seu cliente volta depois de pagar. |
| url_cancelacion | https | Para onde volta se desistir. |
| expira_minutos | inteiro | Entre 1 e 1440. Padrão 15. |
| metadata | objeto | O que você quiser guardar. Devolvemos igual no webhook. |
curl -X POST https://api.toki.lat/v1/cobros \
-H "Authorization: Bearer $TOKI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"monto": 15990,
"concepto": "Nota 4471",
"referencia_externa": "b-4471",
"url_retorno": "https://minhaloja.com/obrigado",
"url_cancelacion": "https://minhaloja.com/carrinho",
"metadata": { "caixa": "3" }
}'{
"cobro": {
"id": "9cd827ca-d10d-4768-8627-265d875f1cd2",
"monto": 15990,
"moneda": "CLP",
"concepto": "Nota 4471",
"estado": "pending",
"referencia_externa": "b-4471",
"metadata": { "caixa": "3" },
"expira_en": "2026-08-25T15:44:57Z",
"creado_en": "2026-08-25T15:29:57Z",
"pagado_en": null,
"url_checkout": "https://toki.lat/pagar/9cd827ca...",
"qr_svg": "https://api.toki.lat/v1/cobros/9cd827ca.../qr",
"deeplink": "toki://pay/9cd827ca..."
}
}Use `url_checkout`: é a página de pagamento que hospedamos. qr_svg e deeplink ficam para quem quiser montar a própria tela — leia a seção seguinte antes de decidir isso.
A página de pagamento
Mandar seu cliente para a url_checkout é a forma recomendada de cobrar, e não só por comodidade.
Um QR não pode ser escaneado pelo mesmo celular que o mostra. Se seu cliente está comprando na sua loja pelo celular — o caso mais comum — uma imagem de QR não serve para nada. Nossa página detecta isso: no celular oferece abrir o app direto, e no desktop mostra o QR.
Ela também acompanha o status sozinha, mostra quanto falta para a cobrança vencer, e devolve o cliente ao seu site quando termina. E como a página é nossa, podemos melhorar o fluxo ou acrescentar meios de pagamento sem que você publique nada de novo.
Incorporar no seu site
Se preferir que seu cliente não saia da sua página, carregue-a em um iframe. Avisamos o resultado com postMessage, então você não precisa consultar nada pelo navegador.
<iframe
src="https://toki.lat/pagar/9cd827ca..."
style="width:100%;max-width:420px;height:620px;border:0"
allow="clipboard-write"></iframe>
<script>
window.addEventListener('message', (e) => {
if (e.origin !== 'https://toki.lat') return; // verifique SEMPRE a origem
if (e.data?.fuente !== 'toki') return;
if (e.data.evento === 'cobro.paid') {
// Confirme contra o SEU servidor antes de entregar: uma mensagem do
// navegador qualquer um pode forjar. Isto serve para reagir na tela.
mostrarObrigado();
}
});
</script>O `postMessage` é para a interface, não para decidir. Antes de entregar um produto, confirme com GET /v1/cobros/{id} do seu servidor ou espere o webhook. Qualquer um pode mandar uma mensagem para sua página; ninguém pode forjar nossa resposta assinada.
O QR
/v1/cobros/{id}/qrDevolve o código em SVG, para ficar nítido tanto em uma bobina térmica quanto em uma tela de caixa. É a única rota que não pede credencial: ela vai impressa em notas, onde não há onde colocar um header. O que expõe é o mesmo id que já vai dentro do código, e com esse id só dá para pagar.
Use quando o pagamento acontece na sua frente — um caixa, uma nota impressa — onde seu cliente tem o próprio celular para escanear. Para cobrar pela internet, use a página de pagamento.
<img src="https://api.toki.lat/v1/cobros/{id}/qr" alt="Pague com Toki" />Consultar uma cobrança
/v1/cobros/{id}Devolve a cobrança com seu estado: pending, paid, cancelled ou expired. É o caminho reserva se você não puder receber webhooks — um caixa atrás de uma rede fechada integra só com isto.
curl https://api.toki.lat/v1/cobros/9cd827ca-d10d-4768-8627-265d875f1cd2 \
-H "Authorization: Bearer $TOKI_API_KEY"Cancelar uma cobrança
/v1/cobros/{id}/anularSó enquanto estiver pending. Uma cobrança já paga não se cancela por aqui: devolver dinheiro é um reembolso, com fluxo próprio. Cancelar uma cobrança paga deixaria a contabilidade dizendo uma coisa e a cobrança outra.
Serviços e assinaturas
Um serviço é algo que as pessoas assinam: um plano, uma associação, uma mensalidade. Você publica pela API e gera um link para alguém assinar. Dali em diante, a cobrança se repete sozinha.
Ninguém fica assinado sem confirmar. O link não ativa nada: a pessoa vê quanto e de quanto em quanto tempo será cobrada, e confirma no app dela. Depois aparece em Dinheiro → Assinaturas, onde ela pode cancelar sem passar por você.
Publicar um serviço
/v1/servicios| Campo | Tipo | O que é |
|---|---|---|
| nombre | texto, obrigatório | O que seu cliente vê. "Plano mensal", "Mensalidade". |
| precio | inteiro, obrigatório | Na menor unidade da sua moeda, igual a `monto`. |
| descripcion | texto | O que inclui. |
| recurrente | booleano | Padrão true. Com false fica publicado mas não aceita assinatura: para cobrar uma vez use /v1/cobros. |
| cada | inteiro | Padrão 1. |
| unidad | texto | day, week, month, semester ou year. Padrão month. |
| dia_de_cobro | inteiro 1–31 | O dia do mês em que todos são cobrados. Só com unidade month, semester ou year. Sem ele, cada pessoa é cobrada no dia em que assinou. |
| dia_de_semana | inteiro 0–6 | Só com unidade week. 0 é domingo. |
| politica_mes_corto | texto | O que fazer quando o dia não existe no mês: last, first_next ou skip. Só com dia_de_cobro acima de 28. |
| referencia_externa | texto | Seu identificador. Torna a criação idempotente. |
Os campos que não correspondem à unidade escolhida são recusados, não ignorados: mandar dia_de_semana em um plano mensal devolve 400, para você não ficar achando que configurou algo.
curl -X POST https://api.toki.lat/v1/servicios \
-H "Authorization: Bearer $TOKI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"nombre": "Plano mensal",
"precio": 19990,
"cada": 1,
"unidad": "month",
"dia_de_cobro": 1,
"referencia_externa": "plano-mensal"
}'{
"servicio": {
"id": "b97c3820-b9ea-4352-aba4-a459aaa02015",
"nombre": "Plano mensal",
"descripcion": null,
"precio": 19990,
"moneda": "CLP",
"recurrente": true,
"cada": 1,
"unidad": "month",
"dia_de_cobro": 1,
"dia_de_semana": null,
"politica_mes_corto": null,
"activo": true,
"referencia_externa": "plano-mensal",
"creado_en": "2026-08-25T15:50:15Z"
}
}Listar e editar
/v1/serviciosLista todo o catálogo do comércio — inclusive o publicado pelo app, não só o criado pela API.
/v1/servicios/{id}| Campo | Tipo | O que é |
|---|---|---|
| activo | booleano | Com false deixa de aceitar novas assinaturas. As vigentes continuam sendo cobradas. |
| precio | inteiro | Vale para quem assinar depois. |
| nombre | texto | |
| descripcion | texto |
Mudar o preço não afeta quem já assinou. O valor dela foi fixado quando aceitou; aumentá-lo daqui seria cobrar algo que ela nunca autorizou.
Gerar o link de assinatura
/v1/suscripciones| Campo | Tipo | O que é |
|---|---|---|
| servicio_id | uuid, obrigatório | O serviço a ser assinado. |
| referencia_externa | texto | Seu identificador. Torna a operação idempotente. |
| expira_minutos | inteiro | Entre 1 e 1440. Padrão 60: assinar se pensa mais do que pagar uma nota. |
curl -X POST https://api.toki.lat/v1/suscripciones \
-H "Authorization: Bearer $TOKI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "servicio_id": "…", "referencia_externa": "socio-118" }'A resposta tem a mesma forma de uma cobrança —mesma url_checkout, mesmos estados— mais o servicio_id. Você consulta o status com GET /v1/cobros/{id} e recebe o webhook cobro.paid quando a pessoa confirma.
Modo de teste
Crie uma credencial em modo teste no seu painel e desenvolva com ela sem movimentar um centavo. A chave é diferente —começa com tk_test_— para não se confundir com a de produção, nem num log nem num arquivo de configuração.
Uma cobrança de teste não pode ser paga com dinheiro real, e uma real não pode ser dada como paga simulando. Os dois cadeados estão no banco, não nesta documentação: não dependem de ninguém tomar cuidado.
Dar uma cobrança de teste como paga
/v1/cobros/{id}/simular-pagoMarca a cobrança como paga e dispara seu webhook, sem escrever um único lançamento contábil. É assim que você testa sua tela de obrigado e seu tratamento do evento antes de cobrar de alguém.
curl -X POST https://api.toki.lat/v1/cobros/{id}/simular-pago \
-H "Authorization: Bearer $TOKI_TEST_KEY"Cobranças de teste vêm com es_prueba: true na resposta. Se sua integração as vê em produção, você subiu a credencial errada.
Reembolsos
/v1/cobros/{id}/reembolsarDevolve o dinheiro de uma cobrança paga. Sem monto, devolve tudo o que resta; com monto, devolve essa parte, e você pode chamar de novo até completar.
| Campo | Tipo | O que é |
|---|---|---|
| monto | inteiro | Quanto devolver, na menor unidade da sua moeda. Omita para devolver tudo o que resta. |
Seu cliente recupera 100% do que pagou, e esse valor sai inteiro da sua carteira. A comissão não volta: a cobrança já foi processada, então já estava ganha. Na prática você devolve um pouco mais do que recebeu — a diferença é a comissão daquela venda.
Vale planejar: para devolver uma venda você precisa ter o valor completo disponível, não só o que recebeu por ela. Se já sacou e não cobre, o reembolso falha com um erro dizendo quanto falta — preferimos isso a deixar um saldo negativo que alguém terá de perseguir depois.
curl -X POST https://api.toki.lat/v1/cobros/{id}/reembolsar \
-H "Authorization: Bearer $TOKI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "monto": 5000 }'A resposta traz reembolsado, o total devolvido até agora. E avisamos por webhook: cobro.partially_refunded enquanto sobrar algo, cobro.refunded quando tudo voltou.
WooCommerce
Se sua loja é WooCommerce, você não precisa escrever código: existe um plugin que faz tudo isso por você.
Baixar o plugin ↓- 1. Instale em Plugins → Adicionar novo → Enviar plugin.
- 2. Vá em WooCommerce → Configurações → Pagamentos → Toki.
- 3. Cole sua credencial de teste e deixe o modo de teste ligado.
- 4. Copie a "URL para avisos" que aparece ali e cole na sua credencial da Toki, junto com o segredo de assinatura.
- 5. Faça um pedido completo.
POST /v1/cobros/{id}/simular-pagodá como pago sem movimentar dinheiro. - 6. Quando funcionar, cole a credencial de produção e desligue o modo de teste.
Faz cobranças, reembolsos totais e parciais pelo próprio pedido, e verifica a assinatura de cada aviso. O pedido é marcado como pago pelo webhook, nunca porque o cliente voltou à loja — voltar não prova que pagou.
Ao salvar, o plugin pergunta à Toki em que moeda você cobra e avisa se não bate com a da sua loja. A Toki não converte moedas, então nesse caso o método não aparece no checkout em vez de cobrar um número na moeda errada.
Webhooks
Se você configurar uma URL https no painel, avisamos ali quando a cobrança muda de estado: cobro.paid, cobro.cancelled ou cobro.expired. O corpo traz o evento e a cobrança completa.
{
"evento": "cobro.paid",
"cobro": { "id": "9cd827ca...", "estado": "paid", "monto": 15990, ... }
}Cada entrega vai assinada no header X-Toki-Firma, na forma t=<epoch>,v1=<hmac>. O HMAC é SHA-256 sobre ${t}.${corpo} com o segredo de webhook da sua credencial. Verifique sempre: sem isso, qualquer um que conheça sua URL pode dizer que pagou.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verificar(corpo, cabecalho, segredo) {
const p = Object.fromEntries(cabecalho.split(',').map((x) => x.split('=', 2)));
const t = Number(p.t);
// O timestamp vai DENTRO do que é assinado: senão, dá para reenviar um
// webhook antigo com um t novo e a assinatura continuaria válida.
if (Math.abs(Date.now() / 1000 - t) > 300) return false;
const esperado = createHmac('sha256', segredo).update(`${t}.${corpo}`).digest('hex');
const a = Buffer.from(esperado, 'hex');
const b = Buffer.from(p.v1 ?? '', 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}Tratamos qualquer 2xx como entregue. Se seu servidor falhar, tentamos de novo em 1, 5, 25 e 125 minutos, e então desistimos: o último erro fica visível no seu painel. Webhooks podem chegar repetidos, então trate o `id` da cobrança como chave e faça seu processamento idempotente.
O que você recebe
De cada cobrança, a Toki desconta sua comissão e liquida o resto na hora. A API não devolve isso como número fixo porque não é: depende do tipo de cobrança, do seu plano, e de qualquer tarifa acordada com você, que prevalece sobre as demais.
Vale saber: cobrar pela API paga a tarifa de venda presencial, a mais baixa do catálogo — porque você está trazendo seu próprio sistema e a Toki só entra com o meio de pagamento. Vender pelo marketplace da Toki, com catálogo e entrega, custa bem mais. As assinaturas têm tarifas próprias, e cobram a adesão diferente das renovações.
A sua, já com tudo aplicado, está no seu painel: Empresa → Dinheiro, junto ao detalhe de cada liquidação.
Erros
Todos os erros têm a mesma forma. O code é estável e pensado para você comparar no seu código; a mensaje é para você ler.
{ "error": { "code": "credencial_invalida", "mensaje": "..." } }| code | HTTP | O que aconteceu |
|---|---|---|
| sin_credencial | 401 | Falta o header Authorization. |
| credencial_invalida | 401 | Chave, segredo ou IP de origem que não batem. Não dizemos qual, de propósito. |
| sin_alcance | 403 | A credencial não tem permissão para essa operação. |
| cuerpo_invalido | 400 | Falta um campo ou ele tem o tipo errado. |
| no_encontrado | 404 | Não existe essa cobrança para esta credencial. |
| estado_invalido | 409 | A cobrança não está mais pendente. |
| demasiadas_solicitudes | 429 | Você passou das 120 chamadas por minuto. |
| error_interno | 500 | Falha nossa. Repetir é seguro se você mandar referencia_externa. |
Antes de ir para produção
- — Guarde o segredo onde guarda os outros segredos, nunca no código nem no front.
- — Configure a lista de IPs. É o cadeado que continua servindo se o segredo vazar.
- — Mande sempre
referencia_externa: é o que impede cobrar duas vezes quando a rede falha no meio. - — Verifique a assinatura de cada webhook, e não decida nada com um
postMessage. - — Tenha um plano para quando o webhook não chegar: consulte o status antes de entregar.
Entrar com o Toki
Deixe que as pessoas entrem no seu site com a conta Toki. Elas aprovam pelo telefone com o rosto, e você recebe apenas os dados que autorizaram.
É OAuth 2.0 padrão (authorization code + PKCE), não um fluxo inventado. Use a biblioteca que você já conhece, as propriedades de segurança já foram estudadas, e quem nunca ouviu falar do Toki consegue integrar sem confiar na nossa criptografia.
Registre a sua aplicação
Em toki.lat/connect você registra o app e recebe um client_id e um client_secret. O segredo aparece uma única vez: guardamos só o hash, então não é que não queiramos mostrá-lo depois — nós não o temos.
Você pode integrar já com as permissões não sensíveis. Para documento, telefone, empresas ou autorizar operações revisamos o app antes: nome, logo e responsável declarado. Um app chamado "Toki Pagamentos" com o nosso logo transformaria a tela de consentimento — justamente onde a pessoa confia — numa ferramenta de phishing.
O fluxo
Você manda a pessoa para a tela de consentimento, ela volta com um código, e o seu servidor troca esse código por um token.
/autorizarhttps://toki.lat/autorizar
?client_id=seu_client_id
&redirect_uri=https://seusite.com/oauth/callback
&response_type=code
&scope=perfil+email
&state=<valor aleatório seu>
&code_challenge=<SHA-256 do verifier, base64url>
&code_challenge_method=S256A pessoa vê o seu nome, o domínio real do seu site, quem responde por ele, e cada dado que você pede em linguagem humana. Se aprovar, volta ao seu redirect_uri com code e o seu state intacto.
/api/oauth/tokengrant_type=authorization_code
code=<o codigo>
redirect_uri=https://seusite.com/oauth/callback
client_id=seu_client_id
client_secret=seu_segredo
code_verifier=<o verifier original>Devolve access_token, refresh_token, expires_in e o scope realmente concedido — que pode ser menor do que você pediu. Leia: é a única forma de saber quais dados você tem de verdade.
/api/oauth/userinfoCom Authorization: Bearer <access_token>. Cada campo só é devolvido se a permissão estiver no token. sub vem sempre: é o identificador estável da pessoa.
/api/oauth/revokePara encerrar a sessão do seu lado. Responde 200 sempre, mesmo para um token que nunca existiu: distingui-los transformaria esta rota num jeito de adivinhar tokens válidos.
Que dados você pode pedir
Peça o mínimo. Cada permissão extra é mais uma pergunta que a pessoa precisa responder antes de aprovar, e mais um motivo para não aprovar.
| Permissão | O que devolve | Revisão |
|---|---|---|
| `perfil` | Nome, foto, usuário e se o Toki verificou a identidade | Não |
| `email` | Não | |
| `rut` | Documento de identidade e seu país | Sim |
| `telefono` | Telefone | Sim |
| `empresa` | Empresas de que participa e seu cargo | Sim |
| `transacciones:autorizar` | Autorizar operações em nome dela | Sim |
O que você precisa saber
- — PKCE é obrigatório e só com
S256.plainé rejeitado. - — O
redirect_urié comparado exatamente com os que você registrou. Sem curingas: aceitá-los deixaria o código chegar a um destino que você não controla. - — O código dura um minuto e é de uso único. Se for trocado duas vezes, assumimos que vazou e revogamos tudo o que foi emitido para essa pessoa no seu app.
- — Os
refresh_tokenrotacionam: cada troca devolve um novo e invalida o anterior. Reutilizar um antigo é tratado como roubo e encerra a sessão. - — O
client_secretnunca vai para o navegador. Se o seu app não puder guardá-lo, a troca funciona mesmo assim com PKCE. - — A pessoa pode retirar o acesso quando quiser pela conta dela, e os tokens vivos são cortados junto. A sua integração tem que sobreviver a isso sem quebrar.