AbuseZero

Send identifiers

Send every identifier you have with each evaluation, including payment methods, hashed phone numbers and your own custom identifiers.

Every identifier you send is checked against your workspace's full claim history on every evaluation. The more you send, the more connections AbuseZero can see. Phone numbers and custom identifiers travel as keyed hashes, so AbuseZero matches them without ever receiving the raw value. For how history and matching fit together, see Evaluations and Custom identifiers.

What you can send

FieldWhat it is
subject.emailRequired. The email address on the account making the claim.
subject.external_idYour user ID for the account.
subject.email_verifiedtrue only when your app has verified that the person owns the email address.
context.ipThe visitor's IP address, IPv4 or IPv6.
context.user_agentThe visitor's user agent string.
context.session_idYour session ID for the visit.
identifiers.device_tokenThe value the browser SDK returns. See Set up browser protection.
identifiers.device_idYour own device identifier, such as a mobile app installation ID.
identifiers.browser_cookie_idYour own first-party browser cookie ID.
identifiers.paymentA payment method fingerprint from your payment processor.
identifiers.phone_hash and identifiers.phone_verifiedA keyed hash of the phone number, and whether your app verified it.
identifiers.customUp to 10 hashed custom identifiers that you registered first.

Field limits are in the Evaluations reference. Every field goes into evaluate() in the AbuseZero Node.js SDK, or the body of POST /v1/evaluations.

Send the user and email

Send the account's user ID and email with every claim. Set email_verified from your own records. AbuseZero doesn't verify ownership itself.

subject: {
  external_id: "user_4821",
  email: "jordan@example.com",
  email_verified: true,
},

Send the IP address

Send the visitor's IP address as your trusted proxy chain reports it, not a header the visitor could set. AbuseZero accepts IPv4 and IPv6. Private, loopback, documentation and other non-routable addresses return IP_NOT_ROUTABLE. If you don't have the address, leave context.ip out. The evaluation still runs.

context: {
  ip: clientIp,
  user_agent: request.headers.get("user-agent") ?? undefined,
},

clientIp stands for the address your proxy setup gives you.

Send a payment fingerprint

If the claim involves a payment method, send the stable fingerprint your payment processor assigns to it, taken from your processor's server-side data. Never send card numbers or other card details.

FieldValue
providerA name for your payment processor. Keep it the same for every claim.
scopeThe processor account that issued the fingerprint.
fingerprintThe processor's fingerprint for the payment method.
identifiers: {
  payment: {
    provider: "card_processor",
    scope: "merchant_2041",
    fingerprint: "fp_7Qk2mV0aZ",
  },
},

Fingerprints match only within the same provider and scope.

Hash and send a phone number

Normalize the number to E.164 format, then hash it on your server with a secret that only you hold, using the AbuseZero Node.js SDK's hashing helpers. Send phone_verified with every phone hash.

import { createPhoneHash } from "abusezero/server";

const phoneHash = createPhoneHash({
  secret: process.env.IDENTIFIER_HASH_SECRET!,
  keyVersion: "v1",
  e164: "+14155550123",
});

const identifiers = {
  phone_hash: phoneHash,
  phone_verified: true,
};

createPhoneHash returns the object AbuseZero expects:

{
  "algorithm": "hmac-sha256",
  "key_version": "v1",
  "value": "55e75c4c6e0f0cbd0b10441559a3cb28f99c1c04837ccbf9ceea1d127448a3cc"
}

keyVersion is your own label for the secret. Set phone_verified to true only when your app has verified the number. A phone_verified without a phone_hash, or a hash without phone_verified, is rejected.

Register and send a custom identifier

Custom identifiers are business identifiers of your own, such as a license or tax ID. Register each key and key version once, in Developers → Custom identifiers, with POST /v1/custom-identifiers in the AbuseZero REST API, with abusezero custom-identifiers register in the AbuseZero CLI, or with abusezero_register_custom_identifier on the AbuseZero MCP server.

await abusezero.registerCustomIdentifier({
  key: "license_id",
  key_version: "v1",
  description: "Software license ID",
}, "register-license-id-v1");

Then hash each value and send up to 10 custom identifiers with an evaluation:

import { createCustomIdentifier } from "abusezero/server";

const identifiers = {
  custom: [
    createCustomIdentifier({
      secret: process.env.IDENTIFIER_HASH_SECRET!,
      keyVersion: "v1",
      key: "license_id",
      value: "LIC-20931",
    }),
  ],
};

A key or key version that isn't registered returns UNSUPPORTED_FIELD. Once registered, a custom identifier can also be used in your own policy rules.

Hash identifiers in other languages

If your server doesn't run Node.js, compute the same values yourself: an HMAC-SHA256 of a fixed message, in lowercase hexadecimal.

IdentifierMessage to hash
Phoneabusezero:phone:e164:v1: followed by the E.164 number
Customabusezero:custom: followed by the key, then :v1:, then the value

In Node.js without the SDK helpers, that is:

import { createHmac } from "node:crypto";

const secret = process.env.IDENTIFIER_HASH_SECRET!;
const hmac = (message: string) => createHmac("sha256", secret).update(message, "utf8").digest("hex");

const phoneHash = {
  algorithm: "hmac-sha256",
  key_version: "v1",
  value: hmac("abusezero:phone:e164:v1:+14155550123"),
};

const license = {
  key: "license_id",
  value_hash: {
    algorithm: "hmac-sha256",
    key_version: "v1",
    value: hmac("abusezero:custom:license_id:v1:LIC-20931"),
  },
};

The v1 inside each message is part of the format. It doesn't change when you change your own key_version.

Keep the same secret and key version for as long as you want new claims to match earlier ones. Values hashed with a new secret, or sent with a new key version, don't match earlier values, so either change starts a new history for that identifier.

Verify your integration

Run a claim with every identifier you send, then open the evaluation with GET /v1/evaluations/{id}. Its inputs object reports each input as received or missing.

inputs
{
  "email": "received",
  "ip": "received",
  "user_id": "received",
  "phone": "received",
  "payment": "received",
  "device": "received",
  "browser": "received"
}

Any input you meant to send that shows missing didn't arrive. Check the field name and where your code sets it. Risk Score and signals explains each input.

How is this guide?

Last updated on

On this page