twstec / kit-webhooks
Webhooks de saída e de entrada do TWS Laravel Starter Kit, sem telas. Saída: endpoints por conta (e por projeto), eventos assinados por endpoint, segredo gerado pelo servidor e guardado cifrado, assinatura HMAC-SHA256 com carimbo de tempo, saída por outbox durável e fila cifrada, novas tentativas co
Requires
- php: ^8.4
- ext-curl: *
- ext-openssl: *
- laravel/framework: ^13.17
- twstec/kit-accounts: ^2.0
- twstec/kit-auth: ^2.0
- twstec/kit-foundation: ^2.0
Requires (Dev)
- laravel/pint: ^1.27
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.7 || ^5.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-14 22:27:48 UTC
README
Parte do TWS Laravel Starter Kit. O código, as issues e os pull requests ficam no monorepo kelvindk9w/tws-laravel-starter-kit (pasta
packages/webhooks); este repositório é o espelho só-leitura publicado a cada versão. Documentação: docs/webhooks.md (saída) e docs/webhooks-entrada.md (entrada) · Segurança: SECURITY.md · Licença: MIT (LICENSE).
Webhooks de saída do TWS Laravel Starter Kit, como pacote Laravel sem
telas: o aplicativo dispara um evento para uma conta e o pacote o entrega,
assinado (HMAC-SHA256 sobre timestamp.corpo), a cada endpoint da conta
que o assina — com outbox durável, fila cifrada, novas tentativas com backoff
exponencial, desativação só depois de falhas seguidas E de um prazo sem
sucesso (com aviso por e-mail; as entregas ficam pausadas e saem ao
reativar),
log de entregas redigido, reenvio auditado e proteção contra SSRF (DNS
revalidado em cada envio e conexão no IP conferido, sem redirect nem proxy).
E webhooks de entrada: o aplicativo declara uma fonte por provedor
(nome, verificador, handler) e o pacote recebe em POST /webhooks/inbound/<fonte>
— sem sessão nem CSRF, com limite por fonte e IP —, confere a allowlist de
IP e o tamanho do corpo, verifica sobre o corpo cru (token em
cabeçalho, HMAC configurável, Standard Webhooks ou chave pública RSA/EC, com
rotação de segredo e janela de tempo), deduplica no banco por (fonte, id
do evento) com INSERT de conflito ignorado, responde 202/200 na hora e
entrega o evento ao handler na fila cifrada, com novas tentativas. O
corpo e os cabeçalhos de assinatura não vão para log nem trilha.
Depende do twstec/kit-accounts (o endpoint é da
conta), do twstec/kit-auth (ação sensível) e do
twstec/kit-foundation (trilhas, redação, correlation id) — e
de nada acima deles; um teste de arquitetura na suíte do pacote garante isso.
- Requisitos: PHP 8.4+ com
curl, Laravel 13,twstec/kit-accounts,twstec/kit-authetwstec/kit-foundation2.x; fila e agendador rodando. - Licença: MIT.
O que o pacote traz
| Peça | O que faz |
|---|---|
Webhooks::dispatch($conta, $evento, $dados, $projeto = null) |
A porta de entrada: grava o evento e as entregas (outbox) na transação em volta e põe as tentativas na fila depois do commit |
Models\WebhookEndpoint, WebhookEvent, WebhookDelivery, WebhookDeliveryAttempt |
Da conta (BelongsToAccount). O segredo e o corpo do evento são cifrados (APP_KEY) e ficam fora da serialização |
Actions\* |
CreateEndpoint, UpdateEndpoint, DeleteEndpoint, SetEndpointStatus, RevealSecret, RotateSecret, SendTestEvent, ResendDelivery e CheckEndpointInput — papel (dono/admin), destino, ação sensível (o token é consumido pela própria Action) e trilha de auditoria |
Security\DestinationPolicy, IpRanges, HostResolver |
A política de destino (SSRF): esquema, host canônico, todos os endereços resolvidos permitidos, metadados de nuvem nunca, redes privadas liberadas só fora de produção |
Signing\Signature, SecretGenerator |
O cabeçalho X-Webhook-Signature: t=…,v1=… (um v1 por segredo vigente) e o segredo whsk_… de 256 bits |
Delivery\DeliverySender, DeliverWebhook, DeliveryQueue, Outbox, RetrySchedule, EndpointHealth, ResponseExcerpt |
O envio (reserva no banco, destino conferido de novo, CURLOPT_RESOLVE + CURLOPT_PREREQFUNCTION, sem redirect nem proxy), o backoff, a desativação com aviso e o trecho da resposta cortado e redigido |
Support\WebhookPanel, WebhookAccess, WebhookAudit, EventCatalog, WebhookLifecycle |
O que as telas mostram (nunca o segredo), quem pode o quê, a trilha, o catálogo e a exclusão com a conta e a pessoa |
Console\DispatchPendingDeliveries, PruneWebhookEvents |
webhooks:dispatch-pending (outbox de volta à fila, a cada minuto) e webhooks:prune (retenção; os eventos sem entrega — todo evento é gravado, com ou sem endpoint — com prazo próprio), agendados pelo pacote |
Mail\EndpointDisabledMail |
O aviso de endpoint desativado, ao dono e aos admins da conta |
Inbound\InboundWebhooks, InboundSource, InboundEvent, Contracts\InboundWebhookHandler |
Webhooks de ENTRADA: registrar uma fonte (pela configuração webhooks.inbound_sources ou pelo código), o evento que o handler recebe (id, tipo, ocorrido em, payload decodificado) e o contrato do handler |
Inbound\Verifiers\SharedSecretHeader, HmacSignature, StandardWebhooks, PublicKeySignature (Contracts\Verifier) |
Os verificadores plugáveis (drivers header_token, hmac, standard_webhooks, public_key): corpo cru, tempo constante, rotação, janela de tempo, recusa sem segredo configurado |
Inbound\InboundReceiver, ProcessInboundWebhook, Models\InboundWebhookEvent, Console\PruneInboundWebhooks |
O recebimento (404/403/413/401/400, deduplicação, 202/200, 503 com a fila fora), o job cifrado e idempotente, o registro de deduplicação (sem o corpo, ou com ele cifrado) e o webhooks:prune-inbound agendado |
examples/verify-signature.php |
O exemplo de verificação para o receptor (tolerância de tempo, tempo constante) — o mesmo código que a suíte usa |
Instalação
composer require "twstec/kit-webhooks:~2.0"
php artisan migrate
Ou pelo instalador do kit: php artisan tws:add webhooks (num aplicativo que
já tem o twstec/kit-accounts) ou php artisan tws:install --with=webhooks.
Depois, para a saída:
- Declare os eventos que o aplicativo emite (
WEBHOOKS_EVENTSouconfig/webhooks.phpdo aplicativo só com a chaveevents— ver personalização). - Dispare com
Webhooks::dispatch(...)dentro da transação da mudança. - Mantenha a fila e o agendador rodando.
- Dê às contas uma tela sobre as Actions (os starters do kit trazem as deles, em Livewire e em React).
Para a entrada:
-
Declare as fontes no
config/webhooks.phpdo aplicativo, só com a chaveinbound_sources(segredos por variável de ambiente, dois na rotação):'inbound_sources' => [ 'pagamentos' => [ 'verifier' => ['driver' => 'header_token', 'header' => 'x-provedor-token', 'secrets' => [env('PAGAMENTOS_WEBHOOK_TOKEN'), env('PAGAMENTOS_WEBHOOK_TOKEN_PREVIOUS')]], 'handler' => App\Webhooks\PagamentosHandler::class, 'event' => ['id' => 'id', 'type' => 'event', 'occurred_at' => 'dateCreated'], ], ],
-
Escreva o handler (
Inbound\Contracts\InboundWebhookHandler): idempotente, valida o payload e, com verificação fraca (token fixo), reconsulta a API do provedor antes de mudar estado. -
Cadastre no provedor
https://<domínio>/webhooks/inbound/pagamentos.
Exemplos de HMAC, Standard Webhooks e chave pública em docs/webhooks-entrada.md.
O que o receptor confere
require 'verify-signature.php'; $ok = verify_webhook_signature($corpoBruto, $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '', $segredo);
E deduplica pelo id do evento (o mesmo em toda tentativa e reenvio). A
ordem de chegada não é garantida: compare o created_at do corpo ou consulte
o estado atual do recurso antes de agir.
Detalhes, o exemplo em Node, a tabela do SSRF e as decisões em
docs/webhooks.md.
Testes
./vendor/bin/pest
A suíte sobe uma aplicação Laravel limpa (Testbench) com o foundation, o auth,
o accounts e este pacote. Os envios de verdade vão para um receptor em
127.0.0.1 (o servidor embutido do PHP) — nada sai da máquina — e o DNS é de
mentira (HostResolver), o que permite provar o rebinding e a conexão no IP
conferido.