Podpis to HMAC-SHA256 surowej treści żądania, wyliczony sekretem webhooka i zapisany w postaci szesnastkowej. Weryfikacja polega na samodzielnym wyliczeniu podpisu i porównaniu go z nagłówkiem X-Infakt-Signature.
Wymagania
- Aktywny webhook i jego sekret w zmiennej
INFAKT_WEBHOOK_SECRET(Dodawanie i aktywacja webhooka).
Kroki
Odczyt surowej treści żądania
Podpis wylicza się z treści w postaci, w jakiej przyszła, zanim framework sparsuje JSON. JSON zapisany ponownie ma inną postać i inny podpis.
Porównanie podpisu
import crypto from "node:crypto"; import express from "express"; const app = express(); app.post("/webhooks/infakt", express.raw({ type: "application/json" }), (req, res) => { const expected = crypto .createHmac("sha256", process.env.INFAKT_WEBHOOK_SECRET) .update(req.body) .digest("hex"); const received = req.get("X-Infakt-Signature") ?? ""; const valid = received.length === expected.length && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected)); if (!valid) return res.sendStatus(401); const payload = JSON.parse(req.body.toString("utf8")); // Obsłuż payload.event i payload.resource. res.sendStatus(200); });import hashlib import hmac import os from flask import Flask, abort, request app = Flask(__name__) @app.post("/webhooks/infakt") def infakt_webhook(): raw = request.get_data() expected = hmac.new(os.environ["INFAKT_WEBHOOK_SECRET"].encode(), raw, hashlib.sha256).hexdigest() received = request.headers.get("X-Infakt-Signature", "") if not hmac.compare_digest(expected, received): abort(401) payload = request.get_json() # Obsłuż payload["event"] i payload["resource"]. return "", 200<?php $raw = file_get_contents('php://input'); $expected = hash_hmac('sha256', $raw, getenv('INFAKT_WEBHOOK_SECRET')); $received = $_SERVER['HTTP_X_INFAKT_SIGNATURE'] ?? ''; if (!hash_equals($expected, $received)) { http_response_code(401); exit; } $payload = json_decode($raw, true); // Obsłuż $payload['event'] i $payload['resource']. http_response_code(200);require "openssl" def verify_infakt_signature(request) raw = request.body.read expected = OpenSSL::HMAC.hexdigest("SHA256", ENV.fetch("INFAKT_WEBHOOK_SECRET"), raw) received = request.headers["X-Infakt-Signature"].to_s ActiveSupport::SecurityUtils.secure_compare(expected, received) endusing System.Security.Cryptography; using System.Text; app.MapPost("/webhooks/infakt", async (HttpRequest request) => { using var reader = new StreamReader(request.Body, Encoding.UTF8); var raw = await reader.ReadToEndAsync(); var secret = Environment.GetEnvironmentVariable("INFAKT_WEBHOOK_SECRET")!; var hash = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(raw)); var expected = Convert.ToHexString(hash).ToLowerInvariant(); var received = request.Headers["X-Infakt-Signature"].ToString().ToLowerInvariant(); var valid = CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(received)); return valid ? Results.Ok() : Results.Unauthorized(); });Odrzucenie niepoprawnych żądań
Przy niezgodnym podpisie aplikacja odpowiada kodem
401i nie przetwarza zdarzenia.
Weryfikacja
Po wystawieniu testowej faktury endpoint powinien przyjąć zdarzenie i odpowiedzieć kodem 200. Następnie można wysłać ręcznie żądanie z nieprawidłowym podpisem:
curl -i -X POST "https://<twój-adres>/webhooks/infakt" \
-H "Content-Type: application/json" \
-H "X-Infakt-Signature: 0000" \
-d '{"event":{"name":"invoice_paid"}}'Odpowiedź 401 oznacza, że weryfikacja działa poprawnie.