ChungWebhooksXác minh Webhooks

Cách xác minh Webhooks với thư viện Svix

Xác minh bằng các thư viện chính thức của chúng tôi

Xác minh bằng cách sử dụng thư viện

npm install svix  
# Or  
yarn add svix  

Sau đó xác minh webhooks bằng mã bên dưới. Payload là nội dung (chuỗi) thô của yêu cầu, và headers là các headers được truyền trong yêu cầu.

Sử dụng nội dung yêu cầu thô

Bạn cần sử dụng nội dung yêu cầu thô khi xác minh webhooks, vì chữ ký mật mã nhạy cảm ngay cả với những thay đổi nhỏ nhất. Bạn nên cẩn thận với các framework phân tích cú pháp yêu cầu dưới dạng JSON và sau đó chuyển đổi nó thành chuỗi vì điều này cũng sẽ phá vỡ việc xác minh chữ ký.

Xem các ví dụ bên dưới về cách lấy nội dung yêu cầu thô với các framework khác nhau.

Chữ ký bạn nên lấy từ nơi bạn đã thêm endpoint, ví dụ: cổng ứng dụng.

import { Webhook } from "svix";
 
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
 
// Tất cả đều được gửi từ 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);
// Báo lỗi nếu có lỗi, trả về nội dung đã được xác minh nếu thành công
wh.verify(payload, headers); 

Ví dụ cụ thể cho từng Framework

Dưới đây là các ví dụ về cách điều chỉnh các ví dụ trên cho framework yêu thích của bạn!

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)
 
    # Làm gì đó với 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)
 
    # Làm gì đó với 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
 
    # Làm gì đó với 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({});
    }
 
    // Làm gì đó với 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({});
    }
 
    // Làm gì đó với message...
 
    res.json({});
}

Node.js (Express)

Lưu ý: Khi tích hợp ví dụ này vào một codebase lớn hơn, bạn sẽ phải đảm bảo không áp dụng middleware express.json() cho route webhook, vì payload phải được truyền đến wh.verify mà không cần phân tích cú pháp trước đó.

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({});
    }
 
    // Làm gì đó với message...
 
    res.json({});
});

Node.js (NestJS)

Khởi tạo ứng dụng với cờ rawBody được đặt thành true. Xem tài liệu NestJS để biết chi tiết.

// 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
    }
 
    // Làm gì đó với message...
  }
}

Go (Standard lib)

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
        }
 
        // Làm gì đó với 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
        }
 
        // Làm gì đó với message...
 
        c.JSON(200, gin.H{})
    })
    r.Run()
}

Rust (axum)

Thêm route webhook_in bên dưới vào router 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;
    }
 
    // Làm gì đó với message...
 
    StatusCode::NO_CONTENT
}

Ruby (Ruby on Rails)

Sau khi thiết lập project, hãy thêm route vào file config/routes.rb ở đầu khối 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
end

Route trên khai báo rằng các yêu cầu POST /webhook được ánh xạ đến hành động index của WebhookController.

Để tạo WebhookController và hành động index của nó, chúng ta sẽ chạy trình tạo controller (với tùy chọn --skip-routes vì chúng ta đã có route phù hợp):

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

Rails sẽ tạo một số file cho bạn:

    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

Bây giờ chúng ta có thể thêm logic xác minh của mình vào file app/controllers/webhook_controller.rb mới được tạo:

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)  
 # Làm gì đó với message...  
 head :no_content rescue head :bad_request end endend  
 

PHP (Laravel)

Trong file routes/api.php của bạn, hãy thêm đoạn sau sau chỉ thị use cuối cùng:

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);
 
        # Làm gì đó với message...
 
        return response()->noContent();
    } catch (WebhookVerificationException $e) {
        return response(null, 400);
    }
});