GeralWebhooksVerificando Webhooks

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.

Use o corpo cru da 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);
    }
});