SDK PHP
O pliic/pliic-php é o SDK oficial em PHP. Ele é para quem quer integrar o Pliic nativamente no backend: em vez de embutir o widget, o seu sistema cria sugestões e tickets em nome dos seus usuários, exibe o mural com estado de voto, responde tickets e consome webhooks, tudo pela API REST.
Se o widget embutido já atende, você não precisa do SDK. Use quando quiser controlar 100% da experiência dentro do seu produto.
Esta página é a referência rápida de cada chamada. Para a receita completa de como montar um painel de ajuda nativo com o SDK, incluindo proxy de chave, identidade do usuário e posse de ticket, veja Construa seu helper nativo com o SDK PHP.
Instalação
Seção intitulada “Instalação”composer require pliic/pliic-phpRequer PHP 8.2+ com ext-curl e ext-json. Nenhuma outra dependência.
Autenticação
Seção intitulada “Autenticação”Crie o cliente com a chave secreta do app (sk_live_..., na aba Instalação do app, onde também fica o início rápido em PHP):
use Pliic\PliicClient;
$pliic = new PliicClient('sk_live_...');Os endpoints exigem escopos na chave (suggestions:read, suggestions:write, tickets:read, tickets:write, etc.) e o recurso de API disponível no seu plano.
Agindo em nome do seu usuário
Seção intitulada “Agindo em nome do seu usuário”Toda escrita aceita um objeto user com a identidade do usuário no seu sistema. O Pliic cria ou reutiliza o usuário correspondente automaticamente (se o e-mail já existir, é a mesma pessoa):
$pliic->suggestions->create([ 'user' => $user, 'title' => 'Modo escuro', 'description' => 'Seria mais confortável à noite.',]);Você não precisa saber o id interno do Pliic, o id é o do seu próprio banco.
Sugestões
Seção intitulada “Sugestões”// Mural com estado de voto do usuário atual$pliic->suggestions->list(['status' => 'planned', 'search' => 'escuro', 'user_id' => 'u_123']);
$pliic->suggestions->get(42, ['user_id' => 'u_123']); // inclui user_has_voted$pliic->suggestions->vote(42, ['user' => $user]); // vota; chamar de novo desfaz$pliic->suggestions->comments(42);$pliic->suggestions->addComment(42, ['user' => $user, 'body' => 'Ótima ideia!']);Tickets
Seção intitulada “Tickets”$pliic->tickets->list(['user_id' => 'u_123']); // tickets daquele usuário$pliic->tickets->create(['user' => $user, 'subject' => 'Erro no checkout', 'body' => '...', 'type' => 'bug']);$pliic->tickets->get(7, ['user_id' => 'u_123']); // 404 se o ticket não for de u_123$pliic->tickets->reply(7, ['user' => $user, 'body' => 'Mais detalhes aqui...']);Passar user_id/user_email em tickets->get() restringe a busca àquele autor: se o ticket existir mas for de outra pessoa, a resposta é 404 (desde a versão 1.0.1 do SDK), o mesmo status de “não existe”. Sem esse parâmetro, a chamada devolve qualquer ticket do app pelo id, então use-o sempre que for o próprio usuário final consultando o ticket.
Só o autor do ticket pode responder por ele, e notas internas da sua equipe nunca aparecem na conversa retornada.
Token do widget (SSO)
Seção intitulada “Token do widget (SSO)”Se você também usa o widget embutido, o SDK minta o userToken no servidor:
use Pliic\UserToken;
$token = UserToken::mint($chaveSecreta, [ 'id' => 'u_123', 'name' => 'Ana',]);Entregue $token ao frontend como userToken do widget.
Webhooks
Seção intitulada “Webhooks”Verifique a assinatura antes de confiar em qualquer payload:
use Pliic\Webhook;use Pliic\Exceptions\SignatureVerificationException;
try { $event = Webhook::constructEvent( $request->getContent(), $request->header('X-Pliic-Signature'), $segredoDoEndpoint, // whsec_... );} catch (SignatureVerificationException $e) { abort(400);}
match ($event->type) { 'suggestion.created' => processarSugestao($event->data), 'ticket.created' => processarTicket($event->data), default => null,};Detalhes do formato da assinatura e dos eventos disponíveis: Webhooks.
Falhas da API viram exceções tipadas, todas filhas de Pliic\Exceptions\ApiErrorException:
| Status | Exceção |
|---|---|
| 401 | AuthenticationException |
403 + error: insufficient_scope | InsufficientScopeException (filha de PermissionException) |
| 403 | PermissionException (recurso não incluído no plano e demais recusas) |
| 404 | NotFoundException |
| 422 | ValidationException ($e->errors() traz os erros por campo) |
| 429 | RateLimitException |
O mapeamento usa o código estável error da resposta, nunca o texto da mensagem. Mudar a redação de um erro não quebra os seus catch. Falhas de rede viram Pliic\Exceptions\TransportException.
Escopo faltando
Seção intitulada “Escopo faltando”Toda chave nova nasce somente leitura: ela tem suggestions:read e tickets:read, mais nada. Por isso o seu primeiro create() falha com 403 até alguém habilitar o escopo de escrita. Não é erro no payload.
use Pliic\Exceptions\InsufficientScopeException;
try { $pliic->tickets->create(['user' => $user, 'subject' => 'Não consigo entrar']);} catch (InsufficientScopeException $e) { $e->requiredScope(); // 'tickets:write' $e->grantedScopes(); // ['suggestions:read', 'tickets:read'] $e->manageScopesUrl(); // link direto para Configurações → Chaves de API → Escopos $e->docsUrl();}Como InsufficientScopeException estende PermissionException, quem já captura PermissionException continua capturando. Use a exceção específica só onde você quiser separar “a chave não tem permissão” de “o plano não inclui esse recurso”.
Para liberar: Configurações → Chaves de API → Escopos, no app a que a chave pertence. Detalhes em Chaves de API.
Testando sua integração
Seção intitulada “Testando sua integração”Desde a versão 1.1.0, o SDK vem com um cliente HTTP falso pronto para os seus testes: Pliic\Testing\FakeHttpClient. Não escreva o seu próprio fake para simular a API — os formatos de resposta divergem sutilmente do que a API real devolve (inclusive campos mais novos, como author), e é exatamente esse tipo de bug de teste-passa-mas-produção-quebra que o FakeHttpClient existe para eliminar: os payloads que ele usa são conferidos contra o OpenAPI real em CI, então não ficam defasados.
Injete o fake no lugar do transporte HTTP de verdade:
use Pliic\PliicClient;use Pliic\Testing\FakeHttpClient;
$fake = new FakeHttpClient();$pliic = new PliicClient('sk_test_fake', 'https://pliic.com', $fake);
$pliic->suggestions->list(); // lista realista, sem nenhuma configuraçãoQuando um teste precisa de um dado específico, semeie o payload:
$fake->seedSuggestion(['id' => 42, 'title' => 'Modo escuro', 'vote_count' => 12]);$pliic->suggestions->get(42); // devolve a sugestão semeada
$fake->seedError(422, 'Invalid', ['title' => ['Título já existe.']]);$pliic->suggestions->create(['user' => $user, 'title' => 'Duplicada']); // lança ValidationException
$fake->seedInsufficientScope('tickets:write');$pliic->tickets->create(['user' => $user, 'subject' => 'Oi']); // lança InsufficientScopeExceptionownedByEmail()/ownedByUserId() simulam a mesma regra de posse da API real (buscar o ticket de outro user_email/user_id dá 404). Depois de configurar um dos dois, o fake passa a negar qualquer requisição que não carregue esse parâmetro exato — inclusive uma chamada que, contra a API de verdade, não seria restrita (a API só valida posse quando user_id/user_email é enviado). Use um new FakeHttpClient() novo entre cenários que precisem dos dois comportamentos no mesmo teste.
failNextWithTransportError() simula uma falha de rede só na próxima chamada. E dá para checar o que foi enviado:
$fake->assertRequested('POST', '/suggestions/42/vote');$fake->assertRequestCount(2);
expect($fake->lastRequestBody())->toBe(['user' => $user, 'title' => 'Modo escuro']);$fake->requests guarda toda chamada feita (method, url, headers, body). Se precisar de um payload fora do fake — por exemplo, para comparar num teste de controller — Pliic\Testing\Fixtures expõe cada um diretamente (Fixtures::suggestion(), Fixtures::ticket(), …).
Integração com Laravel
Seção intitulada “Integração com Laravel”Desde a versão 1.2.0, o SDK traz uma ponte opcional para Laravel, descoberta automaticamente pelo composer: ela só carrega quando a sua aplicação hospedeira é um projeto Laravel, e nunca adiciona illuminate/support como dependência obrigatória do SDK, então quem usa PHP puro não é afetado. Requer Illuminate ^11 ou superior.
1. Instale (mesmo comando de sempre):
composer require pliic/pliic-php2. Publique a config e defina as variáveis de ambiente:
php artisan vendor:publish --tag=pliic-configPLIIC_API_KEY=sk_live_...PLIIC_BASE_URL=https://pliic.comPLIIC_WEBHOOK_SECRET=whsec_...Pliic\PliicClient já sai registrado como singleton no container, resolvível em qualquer lugar da aplicação:
use Pliic\PliicClient;
$pliic = app(PliicClient::class);3. Registre a rota de webhook e escute o evento:
use Pliic\Laravel\Pliic;
Pliic::webhooks('/webhooks/pliic'); // POST /webhooks/pliicA rota já verifica o X-Pliic-Signature por você e dispara Pliic\Laravel\Events\WebhookReceived — sua aplicação nunca lida com o payload bruto nem com a checagem de assinatura. Ela também já sai isenta de CSRF, então pode ficar tranquilamente em routes/web.php sem configuração extra.
use Illuminate\Support\Facades\Event;use Pliic\Laravel\Events\WebhookReceived;
Event::listen(WebhookReceived::class, function (WebhookReceived $received): void { match ($received->event->type) { 'suggestion.created' => notificarEquipeDeNovaSugestao($received->event->data), default => null, };});author vs sender: evitando notificar a própria pessoa
Seção intitulada “author vs sender: evitando notificar a própria pessoa”Todo payload de webhook traz um author (dono do registro) e um sender (quem disparou este evento específico) — podem ser a mesma pessoa ou não. Compare os dois antes de notificar, para não avisar alguém sobre a própria ação:
use Illuminate\Support\Facades\Event;use Pliic\Laravel\Events\WebhookReceived;
Event::listen(WebhookReceived::class, function (WebhookReceived $received): void { if ($received->event->type !== 'suggestion.commented') { return; }
$author = $received->event->data['author']; // ['external_id' => ..., 'name' => ...] $sender = $received->event->data['sender']; // ['type' => 'app_user'|'member', 'external_id' => ..., 'name' => ...]
if ($sender['external_id'] === $author['external_id']) { return; // o autor comentou na própria sugestão — nada a notificar }
notificar($author, "{$sender['name']} comentou na sua sugestão.");});sender['type'] vem 'member' quando alguém da sua equipe agiu pelo painel do Pliic (external_id sempre null, membros não são usuários do app) e 'app_user' quando foi o próprio usuário final. Mais sobre os dois campos: Identificando quem fez o quê.
Frontend em JavaScript?
Seção intitulada “Frontend em JavaScript?”Para construir a interface no navegador (em vez do backend), use o @pliic/sdk, que fala com a API do widget usando a chave pública.
Próximos passos
Seção intitulada “Próximos passos”- Construa seu helper nativo com o SDK PHP: a receita completa, com proxy de chave, identidade do usuário, posse de ticket e um prompt pronto pra IA gerar o helper.