Guides
AbuseGraph + Clerk
Score Clerk signups and logins via webhooks — store browser SDK signals in unsafe_metadata for a full score.
Quick path
- Add
sk_test_…,pk_test_…, andABUSEGRAPH_SITEto env. - On signup/login UI, run the SDK and store
toCheckBodyinunsafe_metadata.abusegraph. - Handle
user.createdandsession.createdwebhooks withcreateCheckClient.
Keys
| Key | Env | Use |
|---|---|---|
| Secret | ABUSEGRAPH_SECRET_KEY (sk_test_… / sk_live_…) | Server /api/v1/check |
| Publishable | NEXT_PUBLIC_ABUSEGRAPH_PUBLISHABLE_KEY (pk_test_…) | Browser SDK |
| Site | ABUSEGRAPH_SITE | DNS-verified domain |
Free includes 1,000 live API checks / month after DNS verify. Test keys never bill.
Env
ABUSEGRAPH_SITE=yourdomain.com
ABUSEGRAPH_CHECK_URL=https://abusegraph.com/api/v1/check
ABUSEGRAPH_SECRET_KEY=sk_test_…
ABUSEGRAPH_SECRET_KEY_TEST=sk_test_…
NEXT_PUBLIC_ABUSEGRAPH_PUBLISHABLE_KEY=pk_test_…Use sk_live_… / pk_live_… in production after DNS verify.
Server check
Use @abusegraph/server createCheckClient. Events: signup | login | password_reset | email_change. Optional sessionId.
import { createCheckClient } from "@abusegraph/server"
const abusegraph = createCheckClient({
secretKey: process.env.ABUSEGRAPH_SECRET_KEY!, // sk_test_…
site: process.env.ABUSEGRAPH_SITE!,
})Browser signals (recommended)
init + collect + evaluate + toCheckBody — NEVER bare sdk.check() alone as the authoritative decision.
import { init } from "@abusegraph/sdk"
const sdk = init({
publicKey: process.env.NEXT_PUBLIC_ABUSEGRAPH_PUBLISHABLE_KEY!, // pk_test_…
})
export async function collectForAuth(email: string, event: "signup" | "login") {
const collected = await sdk.collect()
const edge = await sdk.evaluate(collected, { email })
const body = sdk.toCheckBody(collected, {
email,
event,
verdictToken: edge.verdictToken.token,
})
// Store on the user before Clerk finishes signup
await clerk.user.update({ unsafeMetadata: { abusegraph: body } })
return body
}Clerk webhooks
user.created (signup)
import { createCheckClient } from "@abusegraph/server"
const abusegraph = createCheckClient({
secretKey: process.env.ABUSEGRAPH_SECRET_KEY!,
site: process.env.ABUSEGRAPH_SITE!,
})
export async function POST(req: Request) {
const evt = await req.json()
if (evt.type !== "user.created") return new Response("ok")
const email = evt.data.email_addresses?.[0]?.email_address
const browser = evt.data.unsafe_metadata?.abusegraph ?? {}
const result = await abusegraph.check({
email,
userId: evt.data.id,
event: "signup",
ip: req.headers.get("x-forwarded-for") ?? undefined,
...browser,
})
if (result.verdict === "block") {
// Ban or delete the Clerk user
}
return Response.json(result)
}session.created (login)
if (evt.type === "session.created") {
const email = evt.data.user?.email_addresses?.[0]?.email_address
const result = await abusegraph.check({
email,
userId: evt.data.user_id,
event: "login",
ip: req.headers.get("x-forwarded-for") ?? undefined,
})
if (result.verdict === "block") {
// Revoke session / ban user
}
}Lifecycle events
- login —
session.createdwebhook withevent: "login". - password_reset — call check from your reset handler or a custom webhook with
event: "password_reset". - email_change — call after Clerk email update with
event: "email_change".
Platform
Lists, rules, and linked accounts apply on every check — configure in the console. Optional session: POST /api/v1/session. Native apps: iOS, Android, React Native.
Verify
Create a test user with sk_test_…, confirm a row in Activity, and verify blocked users are removed or banned.