Webhook-allekirjoitusten todentaminen
Jokaisessa toimituksessa on FeitPay-Signature-otsake:
FeitPay-Signature: t=1760961600,v1=186aa47526353adc39a808f8e0bd84f501a5a6a1f483572b1f8a3eab3ed44cc8Algoritmi
Osio nimeltä “Algoritmi”- Lue pyynnön raaka runko täsmälleen sellaisena kuin se saapui (tavuina). Älä jäsennä ja sarjallista JSONia ensin uudelleen — mikä tahansa muutos välilyönneissä tai avainten järjestyksessä rikkoo allekirjoituksen.
- Jäsennä otsake:
ton Unix-aikaleima; jokainenv1on heksadesimaalinen allekirjoitus. Ohita tuntemattomat skeemat. - Muodosta allekirjoitettu viesti:
<t>+"."+<raaka runko>. - Laske
HMAC-SHA256(avain = koko salaisuus "whsec_…" UTF-8-muodossa, viesti)ja koodaa tulos heksadesimaaliksi. - Vertaa jokaiseen
v1-arvoon vakioaikaisella vertailulla. Mikä tahansa vastaavuus kelpaa (vaihdon aikana arvoja on useita). - Hylkää, jos
|nyt − t|on suurempi kuin toleranssisi (oletus 300 sekuntia) — tämä estää toistohyökkäykset. - Vasta nyt jäsennä JSON ja käsittele tapahtuma ohittaen kaksoiskappaleet
event_id:n perusteella.
Virallisten kirjastojen virhesyyt: malformed_header, no_signatures, signature_mismatch,
timestamp_outside_tolerance, invalid_payload. Vastaa niihin kaikkiin 400.
Valmiit todentajat
Osio nimeltä “Valmiit todentajat”import { createServer } from 'node:http';import { verifyWebhook, WebhookVerificationError } from '@feitpay/webhooks';
const secrets = [process.env.FEITPAY_WEBHOOK_SECRET!]; // add the previous secret while rotating
createServer(async (req, res) => { const chunks: Buffer[] = []; for await (const chunk of req) chunks.push(chunk as Buffer); const raw = Buffer.concat(chunks); // the RAW body
try { const event = await verifyWebhook({ payload: raw, header: req.headers['feitpay-signature'] as string, secrets }); // dedupe on event.event_id, then handle it res.writeHead(200).end(); } catch (err) { const reason = err instanceof WebhookVerificationError ? err.reason : 'error'; res.writeHead(400, { 'Content-Type': 'application/json' }).end(JSON.stringify({ error: reason })); }}).listen(4400);Next.js-reittikäsittelijöissä käytä await request.text() (ei request.json()) saadaksesi raa’an rungon.
Synkroninen Node-versio on saatavilla paketista @feitpay/webhooks/node (verifyWebhookSync).
import "github.com/feitpay/feitpay-go/webhook"
func handle(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20)) if err != nil { http.Error(w, "payload too large", http.StatusRequestEntityTooLarge) return } secrets := []string{os.Getenv("FEITPAY_WEBHOOK_SECRET")} event, err := webhook.Verify(body, r.Header.Get(webhook.SignatureHeader), secrets, nil) if err != nil { http.Error(w, string(webhook.ReasonOf(err)), http.StatusBadRequest) return } // dedupe on event.EventID, then handle it w.WriteHeader(http.StatusOK)}import osfrom feitpay import webhooksfrom feitpay.webhooks import WebhookVerificationError
SECRETS = [os.environ["FEITPAY_WEBHOOK_SECRET"]]
def handle(raw_body: bytes, signature_header: str) -> tuple[int, dict]: try: event = webhooks.verify(raw_body, signature_header, SECRETS) except WebhookVerificationError as exc: return 400, {"error": exc.reason} # dedupe on event["event_id"], then handle it return 200, {"received": True}signed = timestamp + "." + raw_bodyexpected = hex( HMAC_SHA256( key = "whsec_…", data = signed ) )valid = any( constant_time_equal(expected, v1) for v1 in header.v1 ) and abs(now - timestamp) <= 300Testivektorit
Osio nimeltä “Testivektorit”Repositoriossa on kaikille kielille yhteiset vektorit tiedostossa packages/test-fixtures/src/webhook-vectors.json —
kelvolliset allekirjoitukset, vaihto kahdella allekirjoituksella, muokattu runko, väärä salaisuus, vanhentunut ja
tuleva aikaleima sekä puuttuva ja virheellinen otsake. TypeScript-, Python- ja Go-kirjastot ajavat kaikki tapaukset
CI:ssä. Jos kirjoitat oman todentajan, aja se samaa tiedostoa vasten.
Lähetä itsellesi allekirjoitettu testi
Osio nimeltä “Lähetä itsellesi allekirjoitettu testi”- Hallintapaneeli: Integrointi → Webhookit → Lähetä testitapahtuma, tai
POST /v1/webhook-endpoints/{id}/test, joka asettaa jonoon allekirjoitetunwebhook.test-toimituksen.
Yksikkötesteissä voit luoda kelvollisia otsakkeita paikallisesti funktioilla generateTestHeader (TypeScript),
generate_test_header (Python) tai webhook.GenerateTestHeader (Go).