> Source: https://kaidn.io/docs/quickstart/go
> Full documentation index: https://kaidn.io/llms.txt

Docs menuAll pages, quickstarts and this page’s contentsDocs
- [Introduction](https://kaidn.io/docs)
- [Quickstarts](https://kaidn.io/docs/quickstart)
- [Core concepts](https://kaidn.io/docs/concepts)
- [API reference](https://kaidn.io/docs/api)
- [API explorer](https://kaidn.io/docs/reference)
- [Guides](https://kaidn.io/docs/guides)
- [Keys & dashboard](https://kaidn.io/docs/keys)
- [Glossary](https://kaidn.io/glossary)

Web
- [JavaScript](https://kaidn.io/docs/quickstart/javascript)
- [React](https://kaidn.io/docs/quickstart/react)
- [Next.js](https://kaidn.io/docs/quickstart/nextjs)
- [Preact](https://kaidn.io/docs/quickstart/preact)
- [Vue](https://kaidn.io/docs/quickstart/vue)
- [Nuxt](https://kaidn.io/docs/quickstart/nuxt)
- [Angular](https://kaidn.io/docs/quickstart/angular)
- [Svelte](https://kaidn.io/docs/quickstart/svelte)

Mobile
- Androidsoon
- iOSsoon
- React Nativesoon
- Fluttersoon

Server
- [Node.js](https://kaidn.io/docs#quickstart)
- [PHP](https://kaidn.io/docs/quickstart/php)
- C#/.NETsoon
- [Go](https://kaidn.io/docs/quickstart/go)
- Javasoon
- [Python](https://kaidn.io/docs/quickstart/python)

On this page
- [1. Get your API key](#step-1)
- [2. Set up your project](#step-2)
- [3. Score your first signup](#step-3)
- [4. Block bots and datacenter traffic](#step-4)
- [5. Stop one person opening many accounts](#step-5)
- [6. Wire it into a handler](#step-6)
- [Next steps](#next)

# Go quickstart

Score a signup from a Go service in about five minutes. The client is`github.com/kaidn-io/kaidn-go`, and it has **no dependencies**: the whole API is JSON over HTTP, so a dependency would buy nothing and cost you a supply chain to audit.

Every snippet below was run against a live API before it was published, and the outputs are transcripts.

## 1. Get your API key

Sign up at [kaidn.io](https://kaidn.io/register). The free tier is 10,000 events a month with no card. From [your dashboard](https://kaidn.io/app/apis) you get two keys, and they are not interchangeable.

| Key | Where it belongs | What it can do |
| --- | --- | --- |
| `kdn_live_…` | your server, only | score, read events, erase data |
| `kdn_pub_…` | safe in a browser | submit fingerprints, nothing else |

This page uses the **secret** key. If you paste a publishable key into a server, `/v1/score` rejects it, which is the failure you want: the opposite mistake, a secret key in a page, is a breach rather than an error.

Terminal

```
export KAIDN_SECRET_KEY="kdn_live_…"
```

## 2. Set up your project

Terminal

```
mkdir kaidn-quickstart && cd kaidn-quickstart
go mod init example.com/kaidn-quickstart
go get github.com/kaidn-io/kaidn-go
```

That is the only `go get` on this page. Go 1.21 or later.

## 3. Score your first signup

One event in, one verdict out. Only `Event` is required; everything else is a signal, and each one you add is another check that can fire.

main.go

```
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	kaidn "github.com/kaidn-io/kaidn-go"
)

func main() {
	client := kaidn.New(kaidn.Options{APIKey: os.Getenv("KAIDN_SECRET_KEY")})

	res, err := client.Score(context.Background(), kaidn.Event{
		Event:    "signup",
		UserID:   "u_2001",
		Email:    "priya@gmail.com",
		IP:       "24.60.140.22",
		DeviceID: "device-priya",
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("score=%d verdict=%s\n", res.Score, res.Verdict)
	for _, c := range res.Checks {
		fmt.Printf("  %-24s %3d  %s\n", c.Reason, c.Weight, c.Message)
	}
}
```

Terminal

```
go run .

score=0 verdict=allow
```

Nothing fired, so nothing is printed under it. A residential IP, a real mailbox and a device nobody has seen before is what an ordinary customer looks like.

## 4. Block bots and datacenter traffic

Change two fields: a hosting IP and a disposable address.

main.go

```
		Email:    "bot@mailinator.com",
		IP:       "45.83.220.1",
```

Terminal

```
go run .

score=80 verdict=block
  datacenter_ip             45  IP is a datacenter/hosting address (hosting), not a residential user
  disposable_email          35  Email uses a disposable/temporary domain
  abusive_email_domain      30  Email domain is on a known-abusive (spam/fraud) list
```

Three checks fired and their weights sum past the block threshold. Note the score is clamped at 100, so the arithmetic will not always add up on the page: the weights are the honest record of what happened, and the score is bookkeeping.

`Checks`is the field worth logging. It is the answer to “why was this account blocked”, and having it already in your logs is the difference between a two-minute support reply and an afternoon of guessing.

## 5. Stop one person opening many accounts

The dominant abuse is not a bot, it is one person with many addresses. Gmail ignores dots and everything after a plus sign, so a single inbox is an unlimited supply.

main.go

```
	// One person, one device, three addresses that all reach one inbox.
	for i, email := range []string{
		"sam.taylor@gmail.com",
		"samtaylor@gmail.com",
		"sam.taylor+trial@gmail.com",
	} {
		res, _ := client.Score(ctx, kaidn.Event{
			Event:    "signup",
			UserID:   fmt.Sprintf("u_30%02d", i),
			Email:    email,
			IP:       "24.60.140.22",
			DeviceID: "device-sam",
		})
		fmt.Printf("%-28s score=%-3d %s\n", email, res.Score, res.Verdict)
	}
```

Terminal

```
go run .

sam.taylor@gmail.com         score=15  allow
    aliased_address       15  Email is a re-spelling of another address that reaches the same inbox

samtaylor@gmail.com          score=60  review
    device_reuse          15  This device is linked to 2 accounts, on different networks
    email_reuse           45  This mailbox is behind 2 accounts

sam.taylor+trial@gmail.com   score=100 block
    device_reuse          15  This device is linked to 3 accounts, on different networks
    ip_velocity           25  5 accounts from this IP in a short window
    plus_addressing       10  Email uses plus-addressing, common when farming accounts
    aliased_address       15  Email is a re-spelling of another address that reaches the same inbox
    email_reuse           55  This mailbox is behind 3 accounts
```

The escalation is the point. The first attempt is allowed, because one alias is not evidence of anything. By the third the evidence has accumulated from four directions at once and it stops.

Look at `device_reuse`: it is **15**, the smallest weight there. That is deliberate. A raw browser fingerprint is a hash of settings, and identical settings produce identical hashes, so on our own traffic one fingerprint covers 2.30 real people on iOS Safari. A device match that could block on its own would be refusing strangers, and the refusals would land almost entirely on one platform. It is evidence, weighted to matter in company. See [device identity](https://kaidn.io/docs/concepts#identity).

## 6. Wire it into a handler

Three things matter in production, and none of them is the scoring call.

handler.go

```
func signup(w http.ResponseWriter, r *http.Request) {
	email := r.FormValue("email")

	res, err := client.Score(r.Context(), kaidn.Event{
		Event:    "signup",
		UserID:   email,
		Email:    email,
		IP:       kaidn.ClientIP(r),          // 1. the REAL client address
		DeviceID: r.FormValue("kaidn_device_id"),
	})

	// 2. FAIL OPEN. An outage at a fraud vendor must never become an
	//    outage in your product.
	if err != nil {
		log.Printf("kaidn unavailable, allowing: %v", err)
		createAccount(email)
		return
	}

	// 3. Three outcomes, not two.
	switch res.Verdict {
	case kaidn.VerdictBlock:
		http.Error(w, "could not create that account", http.StatusForbidden)
	case kaidn.VerdictReview:
		createAccount(email)              // create it, hold what is worth stealing
		holdTrial(email, res.EventID)
	default:
		createAccount(email)
	}
}
```

| | Why it matters |
| --- | --- |
| `kaidn.ClientIP(r)` | Behind a load balancer r.RemoteAddr is the balancer. Every user then shares one address, the IP checks correlate everyone with everyone, and you conclude the product is broken. |
| Fail open | The client already retried the transient cases, so an error reaching you means the service is genuinely unreachable. Allow and flag; never refuse the user. |
| Three verdicts | review is where a hold, a verification step or a human look is proportionate. Collapsing it forces every uncertain signup into a false positive or a free pass. |

## Next steps

The client covers all 23 endpoints. Two worth knowing about early:

more.go

```
// Is this address disposable? No event scored, nothing billed.
e, _ := client.CheckEmail(ctx, "a.b+tag@mailinator.com")
e.Email.IsDisposable   // true
e.Email.Canonical      // dots and plus-tags stripped

// What has this device actually been seen doing? The call that makes a
// device match interpretable rather than merely true.
obs, _ := client.DeviceObservations(ctx, deviceID)

// Backfill: one call, not one per row.
out, _ := client.BatchScore(ctx, rows)
```

- [A full worked example](https://kaidn.io/blog/device-fingerprinting-go) with a browser collector and a demo repo you can clone.
- [Package reference on pkg.go.dev](https://pkg.go.dev/github.com/kaidn-io/kaidn-go)
- [Device identity](https://kaidn.io/docs/concepts#identity): the three resolution rungs and the measured collision risk of each.
