Como Verificar Webhooks com as Bibliotecas Svix
Verificando usando nossas bibliotecas oficiais
Verificação usando bibliotecas
npm install svix
# Or
yarn add svix
Então verifique webhooks usando o código abaixo. O payload é o corpo cru (string) da requisição, e os headers são os headers passados na requisição.
Você precisa usar o corpo cru da requisição ao verificar webhooks, pois a assinatura criptográfica é sensível até mesmo às menores mudanças. Você deve ficar atento a frameworks que analisam a requisição como JSON e depois a convertem em string, pois isso também quebrará a verificação da assinatura.
Veja exemplos abaixo de como obter o corpo cru da requisição com diferentes frameworks.
A assinatura que você deve obter de onde você adicionou o endpoint, por exemplo, o portal do aplicativo.
import { Webhook } from "svix";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
// Estes foram todos enviados do servidor
const headers = {
"svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"svix-timestamp": "1614265330",
"svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"test": 2432232314}';
const wh = new Webhook(secret);
// Lança um erro, retorna o conteúdo verificado com sucesso
wh.verify(payload, headers);
Exemplos específicos de framework
Aqui estão exemplos de como ajustar os exemplos acima para seu framework favorito!
Python (Django)
from django.http import HttpResponse
from svix.webhooks import Webhook, WebhookVerificationError
secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
@csrf_exempt
def webhook_handler(request):
headers = request.headers
payload = request.body
try:
wh = Webhook(secret)
msg = wh.verify(payload, headers)
except WebhookVerificationError as e:
return HttpResponse(status=400)
# Faça algo com a mensagem...
return HttpResponse(status=204)
Python (Flask)
from flask import request
from svix.webhooks import Webhook, WebhookVerificationError
secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
@app.route('/webhook/')
def webhook_handler():
headers = request.headers
payload = request.get_data()
try:
wh = Webhook(secret)
msg = wh.verify(payload, headers)
except WebhookVerificationError as e:
return ('', 400)
# Faça algo com a mensagem...
return ('', 204)
Python (FastAPI)
from fastapi import Request, Response, status
from svix.webhooks import Webhook, WebhookVerificationError
secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
@router.post("/webhook/", status_code=status.HTTP_204_NO_CONTENT)
async def webhook_handler(request: Request, response: Response):
headers = request.headers
payload = await request.body()
try:
wh = Webhook(secret)
msg = wh.verify(payload, headers)
except WebhookVerificationError as e:
response.status_code = status.HTTP_400_BAD_REQUEST
return
# Faça algo com a mensagem...
Node.js (Next.js)
import { Webhook } from "svix";
import { buffer } from "micro";
export const config = {
api: {
bodyParser: false,
},
}
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
export default async function handler(req, res) {
const payload = (await buffer(req)).toString();
const headers = req.headers;
const wh = new Webhook(secret);
let msg;
try {
msg = wh.verify(payload, headers);
} catch (err) {
res.status(400).json({});
}
// Faça algo com a mensagem...
res.json({});
}
Node.js (Next.js 13 App Router)
import { Webhook } from "svix";
const webhookSecret: string = process.env.WEBHOOK_SECRET || "your-secret";
export async function POST(req: Request) {
const svix_id = req.headers.get("svix-id") ?? "";
const svix_timestamp = req.headers.get("svix-timestamp") ?? "";
const svix_signature = req.headers.get("svix-signature") ?? "";
const body = await req.text();
const sivx = new Webhook(webhookSecret);
let msg;
try {
msg = sivx.verify(body, {
"svix-id": svix_id,
"svix-timestamp": svix_timestamp,
"svix-signature": svix_signature,
});
} catch (err) {
return new Response("Bad Request", { status: 400 });
}
console.log(msg);
// Resto
return new Response("OK", { status: 200 });
}
Node.js (Netlify Functions)
import { Webhook } from "svix";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
export const handler = async ({body, headers}) => {
const payload = body;
const wh = new Webhook(secret);
let msg;
try {
msg = wh.verify(payload, headers);
} catch (err) {
res.status(400).json({});
}
// Faça algo com a mensagem...
res.json({});
}
Node.js (Express)
Nota: Ao integrar este exemplo em uma base de código maior, você precisará garantir que o middleware express.json()
não seja aplicado à rota do webhook, pois o payload deve ser passado para wh.verify
sem nenhuma análise prévia.
import { Webhook } from "svix";
import bodyParser from "body-parser";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
app.post('/webhook', bodyParser.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body;
const headers = req.headers;
const wh = new Webhook(secret);
let msg;
try {
msg = wh.verify(payload, headers);
} catch (err) {
res.status(400).json({});
}
// Faça algo com a mensagem...
res.json({});
});
Node.js (NestJS)
Inicialize o aplicativo com o sinalizador rawBody
definido como true. Consulte a documentação do NestJS para obter detalhes.
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(
AppModule,
{ rawBody: true } // adicionar o sinalizador rawBody
);
await app.listen(3000);
}
bootstrap();
// webhook.controller.ts
import { Controller, Post, RawBodyRequest, Req } from '@nestjs/common';
import { Request } from 'express';
import { Webhook } from 'svix';
@Controller('webhook')
class WebhookController {
@Post()
webhook(@Req() request: RawBodyRequest<Request>) {
const secret = 'whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw';
const wh = new Webhook(secret);
const payload = request.rawBody.toString('utf8');
const headers = request.headers;
let msg;
try {
msg = wh.verify(payload, headers);
} catch (err) {
// lidar com o erro
}
// Faça algo com a mensagem...
}
}
Go (biblioteca padrão)
package main
import (
"io"
"log"
"net/http"
svix "github.com/svix/svix-webhooks/go"
)
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
func main() {
wh, err := svix.NewWebhook(secret)
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
headers := r.Header
payload, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
err = wh.Verify(payload, headers)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Faça algo com a mensagem...
w.WriteHeader(http.StatusNoContent)
})
http.ListenAndServe(":8080", nil)
}
Go (Gin)
package main
import (
"io"
"log"
"net/http"
"github.com/gin-gonic/gin"
svix "github.com/svix/svix-webhooks/go"
)
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
func main() {
wh, err := svix.NewWebhook(secret)
if err != nil {
log.Fatal(err)
}
r := gin.Default()
r.POST("/webhook", func(c *gin.Context) {
headers := c.Request.Header
payload, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
err = wh.Verify(payload, headers)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Faça algo com a mensagem...
c.JSON(200, gin.H{})
})
r.Run()
}
Rust (axum)
Adicione a rota webhook_in
abaixo a um roteador axum.
use axum::{body::Bytes, http::StatusCode};
use hyper::HeaderMap;
pub const SECRET: &'static str = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
pub async fn webhook_in(headers: HeaderMap, body: Bytes) -> StatusCode {
let wh = svix::webhooks::Webhook::new(SECRET);
if let Err(_) = wh {
return StatusCode::INTERNAL_SERVER_ERROR;
}
if let Err(_) = wh.verify(&body, &headers) {
return StatusCode::BAD_REQUEST;
}
// Faça algo com a mensagem...
StatusCode::NO_CONTENT
}
Ruby (Ruby on Rails)
Depois de configurar seu projeto, adicione uma rota ao seu arquivo config/routes.rb
no início do bloco Rails.application.routes.draw
:
Rails.application.routes.draw do
post "/webhook", to: "webhook#index"
# Para detalhes sobre o DSL disponível neste arquivo, consulte https://guides.rubyonrails.org/routing.html
end
A rota acima declara que as solicitações POST /webhook
são mapeadas para a ação de índice de WebhookController
.
Para criar WebhookController
e sua ação de índice, executaremos o gerador de controlador (com a opção --skip-routes
porque já temos uma rota apropriada):
bin/rails generate controller Webhook index --skip-routes
Rails criará vários arquivos para você:
create app/controllers/webhook_controller.rb
invoke erb
create app/views/webhook
create app/views/webhook/index.html.erb
invoke test_unit
create test/controllers/webhook_controller_test.rb
invoke helper
create app/helpers/webhook_helper.rb
invoke test_unit
invoke assets
invoke scss
create app/assets/stylesheets/webhook.scss
Agora podemos adicionar nossa lógica de verificação ao arquivo app/controllers/webhook_controller.rb
recém-criado:
require 'svix'
class WebhookController < ApplicationController
protect_from_forgery with: :null_session # desabilita o middleware CSRF; necessário para endpoints de API
def index begin payload = request.body.read headers = request.headers wh = Svix::Webhook.new("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw")
json = wh.verify(payload, headers)
# Faça algo com a mensagem...
head :no_content rescue head :bad_request end endend
PHP (Laravel)
No seu arquivo routes/api.php
, adicione o seguinte após a última diretiva use:
use Svix\Webhook;
use Svix\Exception\WebhookVerificationException;
Route::post('webhook', function(Request $request) {
$payload = $request->getContent();
$headers = collect($request->headers->all())->transform(function ($item) {
return $item[0];
});
try {
$wh = new Webhook("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw");
$json = $wh->verify($payload, $headers);
# Faça algo com a mensagem...
return response()->noContent();
} catch (WebhookVerificationException $e) {
return response(null, 400);
}
});