Receive and verify FingerPrint Checker webhooks in Go
Updated
Register an HTTPS endpoint in the console, then handle it in Go in four steps: read the raw body, check the FPC-Signature value with fpchecker.VerifyWebhookSignature, ignore an event you have already seen, and answer 2xx within 3 seconds. Do the slow work after you answer. Anything else is retried.
Add the endpoint
In the console, open the project and add a webhook endpoint under Webhooks and retention. The URL must be https on port 443 with a public hostname. The signing secret (whsec_) is shown once when you add the endpoint, so store it in your secret store. Send test event posts a sample marked test: true.
go get github.com/AndrewYukon/FingerPrintChecker/server/[email protected]Verify, then trust
Every request carries FPC-Signature, FPC-Event-Id and FPC-Delivery-Id. Verify the signature on the exact bytes you received before you parse anything: a re-serialised body will not match. The SDK also rejects a request whose time is more than 5 minutes from yours.
package main
import (
"encoding/json"
"errors"
"io"
"log"
"net/http"
"os"
"time"
fpchecker "github.com/AndrewYukon/FingerPrintChecker/server/go"
)
var secret = os.Getenv("FPC_WEBHOOK_SECRET") // whsec_..., shown once in the console
type event struct {
EventID string `json:"eventId"`
VisitorID string `json:"visitorId"`
LinkedID string `json:"linkedId"`
Test bool `json:"test"`
}
func webhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
if err != nil {
http.Error(w, "bad body", http.StatusBadRequest)
return
}
err = fpchecker.VerifyWebhookSignature(body, r.Header.Get("FPC-Signature"), secret, 5*time.Minute, time.Now())
if err != nil {
var bad *fpchecker.WebhookSignatureError
if errors.As(err, &bad) {
log.Printf("webhook refused: %s", bad.Code)
}
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
var ev event
if err := json.Unmarshal(body, &ev); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
if !firstTime(ev.EventID) { // your store; duplicates are normal
w.WriteHeader(http.StatusOK)
return
}
go process(ev) // slow work after the answer
w.WriteHeader(http.StatusOK)
}Answer fast, expect repeats
Answer any 2xx within 3 seconds. Other answers and timeouts are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, six attempts in all. Redirects are not followed. An endpoint that fails every delivery for 3 days is disabled, and the console shows why and keeps the last 50 deliveries.
Delivery is at least once, so the same event can arrive twice. Use eventId (or FPC-Event-Id) as the key and make the handler safe to run again.
What to do with an event
Each event is the identify that just happened: eventId, visitorId, linkedId, tags, platform and time. Typical uses are to log it next to your own order or user record, to alert on a flagged event, or to keep a risk table up to date. The decision to allow a trial still comes from the eligibility call on your backend, not from a webhook.
- Skip events with test: true when you only want real traffic.
- Rotating the secret takes effect at once, with no grace period: deploy the new secret first, then rotate.
- Answer 401 to a bad signature and do nothing else with the request.
Frequently asked questions
Why must I use the raw body?
The signature covers the exact bytes that were sent. Parsing the JSON and writing it back changes spacing and key order, so the check fails.
Can I verify without the SDK?
Yes. The developer page shows the same check in Node.js, Python and Go using only the standard library.
What happens if my service is down?
Failed deliveries are retried for several hours. After 3 days of failures the endpoint is disabled. Events are also stored, so you can read what you missed from the events API.
Try FingerPrint Checker free
Every new account gets 2,000 free checks. No card required to start.
Create a free account