GeneralWebhooksVerificando Webhooks

Cómo verificar webhooks con las bibliotecas Svix

Verificación usando nuestras bibliotecas oficiales

Verificación utilizando bibliotecas

npm install svix  
# Or  
yarn add svix  

Luego verifica los webhooks usando el código a continuación. La carga útil es el cuerpo (cadena) sin procesar de la solicitud, y los encabezados son los encabezados pasados en la solicitud.

Usa el cuerpo de la solicitud sin procesar

Debes usar el cuerpo de la solicitud sin procesar al verificar los webhooks, ya que la firma criptográfica es sensible incluso a los cambios más mínimos. Debes tener cuidado con los frameworks que analizan la solicitud como JSON y luego la convierten en una cadena porque esto también romperá la verificación de la firma.

Mira los ejemplos a continuación sobre cómo obtener el cuerpo de la solicitud sin procesar con diferentes frameworks.

La firma que debes obtener de donde agregaste el punto final, p. ej., el portal de la aplicación.

import { Webhook } from "svix";
 
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
 
// Estos fueron enviados desde el 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);
// Lanza un error, devuelve el contenido verificado si tiene éxito
wh.verify(payload, headers); 

Ejemplos específicos del framework

¡Aquí hay ejemplos de cómo ajustar los ejemplos anteriores a tu 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)
 
    # Haz algo con el mensaje...
 
    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)
 
    # Haz algo con el mensaje...
 
    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
 
    # Haz algo con el mensaje...

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({});
    }
 
    // Haz algo con el mensaje...
 
    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({});
    }
 
    // Haz algo con el mensaje...
 
    res.json({});
}

Node.js (Express)

Nota: Al integrar este ejemplo en una base de código más grande, deberás asegurarte de no aplicar el middleware express.json() a la ruta del webhook, porque la carga útil debe pasarse a wh.verify sin ningún análisis previo.

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({});
    }
 
    // Haz algo con el mensaje...
 
    res.json({});
});

Node.js (NestJS)

Inicializa la aplicación con la bandera rawBody establecida en true. Consulta la documentación de NestJS para obtener más detalles.

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
 
async function bootstrap() {
  const app = await NestFactory.create(
    AppModule,
    { rawBody: true } // agregar bandera 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) {
      // manejar error
    }
 
    // Haz algo con el mensaje...
  }
}

Go (biblioteca estándar)

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
        }
 
        // Haz algo con el mensaje...
 
        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
        }
 
        // Haz algo con el mensaje...
 
        c.JSON(200, gin.H{})
    })
    r.Run()
}

Rust (axum)

Agrega la ruta webhook_in a continuación a un enrutador 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;
    }
 
    // Haz algo con el mensaje...
 
    StatusCode::NO_CONTENT
}

Ruby (Ruby on Rails)

Una vez que hayas configurado tu proyecto, agrega una ruta a tu archivo config/routes.rb en la parte superior del bloque Rails.application.routes.draw:

Rails.application.routes.draw do
  post "/webhook", to: "webhook#index"
 
  # Para detalles sobre el DSL disponible dentro de este archivo, consulta https://guides.rubyonrails.org/routing.html
end

La ruta anterior declara que las solicitudes POST /webhook se asignan a la acción de índice de WebhookController.

Para crear WebhookController y su acción de índice, ejecutaremos el generador de controladores (con la opción --skip-routes porque ya tenemos una ruta apropiada):

bin/rails generate controller Webhook index --skip-routes  

Rails creará varios archivos por ti:

    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

Ahora podemos agregar nuestra lógica de verificación al archivo app/controllers/webhook_controller.rb recién creado:

require  'svix'  
  
class  WebhookController  < ApplicationController  
 protect_from_forgery with:  :null_session  # desactiva el middleware CSRF; requerido para los puntos finales de la API  
 def  index begin payload = request.body.read headers = request.headers wh = Svix::Webhook.new("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw")  
 json = wh.verify(payload, headers)  
 # Haz algo con el mensaje...  
 head :no_content rescue head :bad_request end endend  
 

PHP (Laravel)

En tu archivo routes/api.php agrega lo siguiente después de la última directiva de uso:

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);
 
        # Haz algo con el mensaje...
 
        return response()->noContent();
    } catch (WebhookVerificationException $e) {
        return response(null, 400);
    }
});