Portal do Cliente
curl --request POST \
--url https://garu.com.br/api/portal/sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"customerId": 123,
"productId": 123,
"returnUrl": "<string>"
}
'import requests
url = "https://garu.com.br/api/portal/sessions"
payload = {
"customerId": 123,
"productId": 123,
"returnUrl": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({customerId: 123, productId: 123, returnUrl: '<string>'})
};
fetch('https://garu.com.br/api/portal/sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://garu.com.br/api/portal/sessions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'customerId' => 123,
'productId' => 123,
'returnUrl' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://garu.com.br/api/portal/sessions"
payload := strings.NewReader("{\n \"customerId\": 123,\n \"productId\": 123,\n \"returnUrl\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://garu.com.br/api/portal/sessions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"customerId\": 123,\n \"productId\": 123,\n \"returnUrl\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://garu.com.br/api/portal/sessions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"customerId\": 123,\n \"productId\": 123,\n \"returnUrl\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyAssinaturas
Portal do Cliente
Permitir que assinantes gerenciem suas assinaturas de forma autônoma
POST
/
api
/
portal
/
sessions
Portal do Cliente
curl --request POST \
--url https://garu.com.br/api/portal/sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"customerId": 123,
"productId": 123,
"returnUrl": "<string>"
}
'import requests
url = "https://garu.com.br/api/portal/sessions"
payload = {
"customerId": 123,
"productId": 123,
"returnUrl": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({customerId: 123, productId: 123, returnUrl: '<string>'})
};
fetch('https://garu.com.br/api/portal/sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://garu.com.br/api/portal/sessions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'customerId' => 123,
'productId' => 123,
'returnUrl' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://garu.com.br/api/portal/sessions"
payload := strings.NewReader("{\n \"customerId\": 123,\n \"productId\": 123,\n \"returnUrl\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://garu.com.br/api/portal/sessions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"customerId\": 123,\n \"productId\": 123,\n \"returnUrl\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://garu.com.br/api/portal/sessions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"customerId\": 123,\n \"productId\": 123,\n \"returnUrl\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyVisão Geral
O Portal do Cliente é uma interface de autoatendimento que permite aos assinantes gerenciar suas assinaturas. Você controla o acesso gerando tokens de sessão seguros.Como Funciona
- Você cria uma sessão do portal via API
- Recebe um token seguro (válido por 1 hora)
- Redireciona o cliente para o portal com o token
- Cliente gerencia sua assinatura conforme sua configuração
Criar Sessão do Portal
Gere um token de acesso para um cliente:curl -X POST https://garu.com.br/api/portal/sessions \
-H "Authorization: Bearer sk_test_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"customerId": 50,
"productId": 123,
"returnUrl": "https://seusite.com/conta"
}'
Parâmetros
integer
required
ID do cliente na Garu.
integer
ID do produto para configuração específica (opcional).
string
URL para redirecionar após ações no portal.
Resposta
{
"sessionId": 789,
"token": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
"expiresAt": "2024-01-15T11:30:00.000Z",
"portalUrl": "/portal?token=a1b2c3d4e5f6..."
}
URL do Portal
https://garu.com.br/portal?token={session_token}
O token expira em 1 hora. Gere um novo token a cada acesso do cliente.
Configurar o Portal
Configuração Global (Padrão para Todos os Produtos)
curl -X PUT https://garu.com.br/api/portal/configuration \
-H "Authorization: Bearer sk_test_sua_chave" \
-H "Content-Type: application/json" \
-d '{
"businessName": "Sua Empresa",
"logoUrl": "https://seu-cdn.com/logo.png",
"primaryColor": "#257264",
"allowCancelSubscription": true,
"allowUpdatePaymentMethod": true,
"allowUpdateBillingInfo": true,
"allowViewInvoices": true,
"cancelAtPeriodEndOnly": true,
"requireCancelReason": false,
"sendCancellationEmail": true,
"sendPaymentMethodUpdatedEmail": true
}'
Configuração por Produto
Para sobrescrever a configuração global por produto (B2B2C — coach/instrutor/professor como Product), use o endpoint canônico documentado em Portal customizado por produto.Na API v1,
{uuid} é o identificador do produto — o identificador estável retornado por GET /api/v1/products e list_products.Opções de Configuração
| Opção | Tipo | Descrição |
|---|---|---|
businessName | string | Nome da empresa exibido no portal |
logoUrl | string | URL do logotipo |
primaryColor | string | Cor primária em hex (padrão: #257264) |
allowCancelSubscription | boolean | Permitir cancelamento |
allowUpdatePaymentMethod | boolean | Permitir trocar cartão |
allowUpdateBillingInfo | boolean | Permitir alterar dados de cobrança |
allowViewInvoices | boolean | Permitir ver histórico de pagamentos |
cancelAtPeriodEndOnly | boolean | Forçar cancelamento apenas no fim do período |
requireCancelReason | boolean | Exigir motivo ao cancelar |
sendCancellationEmail | boolean | Enviar email ao cancelar |
sendPaymentMethodUpdatedEmail | boolean | Enviar email ao trocar cartão |
customWelcomeText | string | Mensagem de boas-vindas personalizada |
customSuccessMessage | string | Mensagem de sucesso personalizada |
customCancellationMessage | string | Mensagem de confirmação de cancelamento |
Ações Disponíveis no Portal
Baseado na sua configuração, clientes podem:| Ação | Flag de Configuração | Padrão |
|---|---|---|
| Cancelar assinatura | allowCancelSubscription | true |
| Atualizar cartão | allowUpdatePaymentMethod | true |
| Atualizar dados de cobrança | allowUpdateBillingInfo | true |
| Ver histórico de pagamentos | allowViewInvoices | true |
| Pausar assinatura | allowCancelSubscription | true |
| Retomar assinatura | allowCancelSubscription | true |
Endpoints do Portal (Token-Based)
Todos os endpoints do portal requerem o token de sessão como query parameter.Obter Dados do Portal
curl -X GET "https://garu.com.br/api/portal?token={session_token}"
Cancelar Assinatura
curl -X POST "https://garu.com.br/api/portal/subscriptions/100/cancel?token={session_token}"
Pausar Assinatura
curl -X POST "https://garu.com.br/api/portal/subscriptions/100/pause?token={session_token}"
Retomar Assinatura
curl -X POST "https://garu.com.br/api/portal/subscriptions/100/resume?token={session_token}"
Reativar Assinatura Cancelada
curl -X POST "https://garu.com.br/api/portal/subscriptions/100/reactivate?token={session_token}"
Atualizar Método de Pagamento
curl -X POST "https://garu.com.br/api/portal/subscriptions/100/update-payment-method?token={session_token}" \
-H "Content-Type: application/json" \
-d '{"paymentMethodId": 456}'
Adicionar Novo Cartão
curl -X POST "https://garu.com.br/api/portal/subscriptions/100/add-card-and-update?token={session_token}" \
-H "Content-Type: application/json" \
-d '{
"cardNumber": "4111111111111111",
"holderName": "JOAO SILVA",
"expiryMonth": 12,
"expiryYear": 2025,
"cvv": "123"
}'
Ver Histórico de Pagamentos
curl -X GET "https://garu.com.br/api/portal/subscriptions/100/events?token={session_token}"
Atualizar Informações do Cliente
curl -X PUT "https://garu.com.br/api/portal/customer-info?token={session_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "João Silva",
"email": "joao@email.com",
"phone": "11999998888"
}'
Integração Frontend
JavaScript
class GaruPortal {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://garu.com.br/api';
this.portalUrl = 'https://garu.com.br/portal';
}
async criarSessao(customerId, options = {}) {
const response = await fetch(`${this.baseUrl}/portal/sessions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
customerId,
productId: options.productId,
returnUrl: options.returnUrl || window.location.href
})
});
return response.json();
}
// Redirecionar para o portal
async redirecionar(customerId, options = {}) {
const session = await this.criarSessao(customerId, options);
window.location.href = `${this.portalUrl}?token=${session.token}`;
}
// Abrir portal em nova janela
async abrirJanela(customerId, options = {}) {
const session = await this.criarSessao(customerId, options);
window.open(
`${this.portalUrl}?token=${session.token}`,
'GaruPortal',
'width=600,height=800'
);
}
}
// Uso
const portal = new GaruPortal('sk_test_sua_chave');
// Botão "Gerenciar Assinatura"
document.getElementById('btn-gerenciar').addEventListener('click', async () => {
await portal.redirecionar(usuarioAtual.garuCustomerId, {
returnUrl: 'https://seusite.com/conta/assinaturas'
});
});
React
import { useCallback } from 'react';
function BotaoPortal({ customerId }) {
const abrirPortal = useCallback(async () => {
const response = await fetch('/api/portal-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ customerId })
});
const { portalUrl } = await response.json();
window.location.href = `https://garu.com.br${portalUrl}`;
}, [customerId]);
return (
<button onClick={abrirPortal}>
Gerenciar Assinatura
</button>
);
}
Python (Flask)
from flask import Flask, redirect, request
import requests
import os
app = Flask(__name__)
GARU_API_KEY = os.environ['GARU_API_KEY']
@app.route('/portal')
def abrir_portal():
customer_id = request.args.get('customer_id')
response = requests.post(
'https://garu.com.br/api/portal/sessions',
headers={
'Authorization': f'Bearer {GARU_API_KEY}',
'Content-Type': 'application/json'
},
json={
'customerId': int(customer_id),
'returnUrl': request.url_root + 'conta'
}
)
session = response.json()
return redirect(f"https://garu.com.br/portal?token={session['token']}")
Boas Práticas de Segurança
Nunca exponha tokens em logs
Nunca exponha tokens em logs
Tokens de sessão são sensíveis. Não os registre em logs de aplicação.
Gere tokens sob demanda
Gere tokens sob demanda
Não pré-gere tokens. Crie um novo a cada acesso do cliente.
Use HTTPS no returnUrl
Use HTTPS no returnUrl
Sempre use URLs seguras para redirecionamento.
Valide propriedade do cliente
Valide propriedade do cliente
Certifique-se de que o cliente pertence à sua conta antes de gerar o token.
Monitore atividade do portal
Monitore atividade do portal
Use webhooks para acompanhar ações dos clientes no portal.
Próximos Passos
Webhooks
Receber notificações de ações no portal
Guia de Assinaturas
Voltar ao guia completo
Was this page helpful?