Protect your own API
Put the same device checks in front of your backend, either as a reverse proxy or as middleware inside your Go service.
Everything in this section so far protects the calls your app makes to Vouch. The reason the module exists is the other traffic: the calls your app makes to your API, where the free trials are claimed, the promo codes are brute forced and the catalogue is scraped.
Two ways to check them, both reading the same policies you already wrote.
The gateway
A reverse proxy in front of any API, in any language. Nothing in your code changes.
Go middleware
One import inside a Go service. No extra hop, and the decision is on the request context.
The gateway
Send traffic to the gateway instead of your API. It refuses what cannot prove itself and forwards the rest.
export VOUCH_SECRET_KEY=sk_live_...
vouch-gateway \
-upstream http://127.0.0.1:3000 \
-app app_1234567890 \
-listen :8080 \
-dry-run-dry-run decides and reports exactly as it would in earnest, and forwards everything anyway. Leave
it on for a day, read the projection, then drop the flag.
Protect deliberately rather than universally. A payment provider's webhook carries no run token and never will, and neither does a health check or an OAuth callback.
-protect '/api/*' -unprotect '/api/health,/api/webhooks/*'The full flag reference lives in the gateway's own README, including fail modes, poll intervals and Docker.
Go middleware
Inside a Go service, skip the extra hop.
guard, err := vouchguard.New(vouchguard.Config{
APIKey: os.Getenv("VOUCH_SECRET_KEY"),
AppID: os.Getenv("VOUCH_APP_ID"),
})
if err != nil {
log.Fatal(err)
}
defer guard.Close()
mux.Handle("/api/", guard.Middleware(http.HandlerFunc(api)))An allowed request carries its decision, so a handler can treat a device that could not fully attest differently from one that proved itself: hold the payout for review, watermark the response, ask for a second factor.
d, ok := vouchguard.DecisionFrom(r.Context())
if ok && d.Verdict != vouchguard.VerdictPass {
// Allowed, and worth treating differently.
}Gin and Echo adapters live in their own modules, so a service that uses neither downloads neither.
No round trip per request
Neither one calls Vouch while your user waits. The first time an enforcer sees a run token it asks us about it once, then verifies every later request from that device locally: signature, clock skew, replay nonce, then policy. A fifteen minute token costs one call and then hundreds of local checks.
Policies and the kill switch are polled every thirty seconds, which is what makes flipping the kill switch in the dashboard land everywhere inside thirty seconds.
When Vouch is unreachable
A device that has already been verified keeps working: its answer is cached until the token itself expires, so an outage at Vouch never logs out customers who are already using your app.
The only case left is a token the enforcer has never seen, at a moment when we cannot be asked about it. You choose what happens:
| Mode | Behaviour | Choose it when |
|---|---|---|
open (default) | Forward it, and log a warning. | Almost always. Our outage must not become your outage. |
closed | Refuse it. | An endpoint that moves money or grants entitlements. |
What a refused request looks like
HTTP/1.1 403 Forbidden
X-Vouch-Refused: signature_invalid
{"error":{"code":"signature_invalid","message":"This request was refused by device security."}}The code is stable, so you can branch on it or alert on it. The message never says which rule fired, offers no retry advice, and does not hint at what a passing request would look like. Telling an attacker which check they failed tells them what to change.
Monitor mode covers this traffic too
Decisions made at the edge are reported back, so the projection in the dashboard describes traffic to your API as well as traffic to ours. Without that you would run in monitor mode, see an empty chart, and conclude that enforcement was safe when it was not.
Reports are queued and batched, never on the request path, and each decision carries its own id, so a batch retried after a lost response is counted once rather than twice.
The enforcer and Vouch evaluate policy identically. That is not left to good intentions: both implementations are held to one shared set of cases, so a rule that logs at the edge in monitor mode is the same rule that blocks there under enforcement.