Cómo verificar webhooks con las bibliotecas de Svix
Verificación mediante bibliotecas
Primero instale las bibliotecas si aún no lo ha hecho:
npm install svix
# Or
yarn add svix Luego verifique los webhooks utilizando el código a continuación. La carga útil es el cuerpo (cadena) sin procesar de la solicitud, y los encabezados son los que se pasan en la solicitud.
Debe utilizar el cuerpo sin procesar de la solicitud al verificar webhooks, ya que la firma criptográfica es sensible incluso a los cambios más mínimos. Debe tener cuidado con los frameworks que analizan la solicitud como JSON y luego la convierten en cadena, porque esto también romperá la verificación de la firma.
Vea ejemplos a continuación sobre cómo obtener el cuerpo sin procesar de la solicitud con diferentes frameworks.
La firma que debe obtener de donde agregó el endpoint, por ejemplo, el portal de la aplicación.
import { Webhook } from "svix";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
// These were all sent from the server
const headers = {
"svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"svix-timestamp": "1614265330",
"svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"test": 2432232314}';
const wh = new Webhook(secret);
// Throws on error, returns the verified content on success
wh.verify(payload, headers); Ejemplos específicos por framework
¡Aquí tiene ejemplos sobre cómo adaptar los ejemplos anteriores a su 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)
# Do something with the message...
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)
# Do something with the message...
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
# Do something with the message...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({});
}
// Do something with the message...
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);
// Rest
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({});
}
// Do something with the message...
res.json({});
}Node.js (Express)
Nota: Al integrar este ejemplo en una base de código más grande, deberá asegurarse de no aplicar el express.json() middleware 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({});
}
// Do something with the message...
res.json({});
});Node.js (NestJS)
Inicialice la aplicación con la bandera rawBody configurada en verdadero. Consulte los documentos 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 } // add rawBody flag
);
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) {
// handle error
}
// Do something with the message...
}
}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
}
// Do something with the message...
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
}
// Do something with the message...
c.JSON(200, gin.H{})
})
r.Run()
}Rust (axum)
Agregue 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;
}
// Do something with the message...
StatusCode::NO_CONTENT
}Ruby (Ruby on Rails)
Una vez que haya configurado su proyecto, agregue una ruta a su archivo config/routes.rb en la parte superior del bloque Rails.application.routes.draw :
Rails.application.routes.draw do
post "/webhook", to: "webhook#index"
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
endLa 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 adecuada):
bin/rails generate controller Webhook index --skip-routes Rails creará varios archivos para usted:
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.scssAhora 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 # disables CSRF middleware; required for API endpoints
def index begin payload = request.body.read headers = request.headers wh = Svix::Webhook.new("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw")
json = wh.verify(payload, headers)
# Do something with the message...
head :no_content rescue head :bad_request end endend
PHP (Laravel)
En su archivo routes/api.php agregue lo siguiente después de la última directiva 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);
# Do something with the message...
return response()->noContent();
} catch (WebhookVerificationException $e) {
return response(null, 400);
}
});