# Go

> Read identification events with a secret key and verify signed webhooks from Go, with context-aware calls and typed events.

Last updated: 2026-09-17

The Go module reads stored events by request ID and verifies webhook signatures. Every call takes a `context.Context`, and the client is safe for concurrent use.

## Requirements

- Go 1.21 or newer.
- A [secret key](https://docs.fingerly.io/docs/api-keys), and a webhook signing secret if you receive [webhooks](https://docs.fingerly.io/docs/webhooks).

## Install

```bash Terminal
go get github.com/fingerly-io/fingerly-go
```

## Read an event

Create one client with your secret key and reuse it. The key decides the regional API and the environment the client reads.

```go Go
import fingerly "github.com/fingerly-io/fingerly-go"

client := fingerly.New(os.Getenv("FINGERLY_SECRET_KEY"))
event, err := client.Events.Get(ctx, "01a0a84b-e6a2-7c09-9f51-0b3d7a26c8e4")
```

An event has the fields listed in [Get an event](https://docs.fingerly.io/reference/get-event#response). `suspect_score` is `null` when the request was not scored.

## Verify a checkout

Read the event your client identified, check it belongs to this action and is recent, then decide on its level. See [server-side verification](https://docs.fingerly.io/docs/server-side-verification).

```go Go
func decide(ctx context.Context, orderID, requestID string) (string, error) {
    event, err := client.Events.Get(ctx, requestID)
    var apiErr *fingerly.APIError
    if errors.As(err, &apiErr) && apiErr.Status == http.StatusNotFound {
        return "refuse", nil
    } else if err != nil {
        return "", err
    }

    if event.Tag != "checkout:"+orderID || time.Since(event.OccurredAt) > 2*time.Minute {
        return "refuse", nil
    }

    switch event.SuspectLevel {
    case "high":
        return "review", nil
    case "medium":
        return "challenge", nil
    }
    return "allow", nil
}
```

## Verify a webhook

Check the signature over the raw request body before parsing it. The helper rejects timestamps more than five minutes from now.

```go Go
func fingerlyWebhook(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "unreadable body", http.StatusBadRequest)
        return
    }
    if !fingerly.VerifyWebhook(
        os.Getenv("FINGERLY_WEBHOOK_SECRET"),
        body,
        r.Header.Get("X-Fingerly-Timestamp"),
        r.Header.Get("X-Fingerly-Signature"),
    ) {
        http.Error(w, "invalid signature", http.StatusBadRequest)
        return
    }

    var event fingerly.WebhookEvent
    _ = json.Unmarshal(body, &event)
    enqueue(event.ID, body)   // deduplicate on the event ID
    w.WriteHeader(http.StatusNoContent)
}
```

## API

| Member | Returns | Notes |
| --- | --- | --- |
| `fingerly.New(secretKey string, opts ...Option)` | `*Client` | Options: `WithEndpoint`, `WithHTTPClient`. |
| `client.Events.Get(ctx, requestID)` | `(*Event, error)` | A non-2xx response returns `*fingerly.APIError` with `Status`. |
| `client.Events.List(ctx, *EventListParams)` | `(*EventPage, error)` | Params: `From`, `To` (`time.Time`), `Page`, `Limit`, `Visitor`, `Level`. |
| `fingerly.VerifyWebhook(secret, body, timestamp, signature)` | `bool` | Five minutes of tolerance. `VerifyWebhookWithTolerance` changes it. |
