Notix
One-time codes

Sign-in codes by SMS or email, checked for fraud first.

Send a one-time code to a phone or an inbox, let Notix turn away risky requests before they cost you, and check what the user typed in one more call. No code is ever stored in plain text.

Two calls

Send a code, check a code

Keep the id from the first call. Pass it back with what the user typed, and the answer says whether it matched and how many tries are left.

Send a code, then check it
import { Notix } from "notix-js";

const notix = new Notix(process.env.NOTIX_API_KEY);

// 1. Send. Email is the default channel.
const { data: sent } = await notix.verify.send({
  to: "+2348031234471",
  channel: "sms",
  appName: "Acme",
  clientIp: request.ip,
});

// 2. Check what the user typed.
const { data: result } = await notix.verify.check({
  id: sent.id,
  code: "482913",
});

if (result?.verified) signIn(user);
Risk checks

Every request is scored before it is sent

Bots that pump your SMS bill and throwaway sign-ups are stopped at the door. Pick a request to see what fires.

Signals that add up

Each signal that fires adds to a score out of 100. The answer carries the score, the level (Low, Medium or High) and every signal, so your app can decide what to do with a Medium.

  • Some signals refuse on their own, like a disposable domain or a premium rate number
  • Strict mode also refuses every High score
  • Always allow up to 50 of your own domains
  • Pass clientIp so the per address check can count
POST /v1/verify/send201 sent
score 100HIGH
role_address+20A role account, not a personal mailbox.
recipient_velocity+40Codes asked for this address again and again.
client_ip_velocity+40Codes asked for from this IP again and again.

Sent, with a High score in the answer. Strict mode would refuse it. Scores use the default weights, which the Notix team can tune.

A refusal you can explain

A refused request costs nothing and creates no code. The answer names the signal, so you can tell the user to try a different address rather than showing a vague error.

A refused request
HTTP/1.1 422 Unprocessable Entity

{
  "error": {
    "code": "RISK_REFUSED",
    "message": "Verification refused: Disposable email domain."
  },
  "risk": {
    "score": 70,
    "level": "HIGH",
    "reasons": [
      { "code": "disposable_domain", "detail": "Disposable email domain." }
    ]
  }
}
Console

Every code, in one place

The One-time codes page shows the week at a glance and each code with its channel, risk, tries and status.

See what is working

Codes sent, verified, the average time to enter one and how many were blocked, with a funnel from sent to verified. Filter by channel. The same figures come from the API with notix.verify.list() and notix.verify.stats().

Your server can follow each code too, with the webhook events verification.sent, verification.verified, verification.failed, verification.refused and verification.delivered.

One-time codes

This week
Risk rules
Sent2,418
Verified2,20791%
Blocked64
RecipientStatusChannelRisk
+234 *** 4471VerifiedSMSLow
maria@example.comVerifiedEmailLow
info@example.comExpiredEmailLow
x7@throwaway.exampleBlockedEmailHigh
tunde@example.comLockedEmailMedium
Options

Your rules, sensible defaults

6digits by default, 4 to 8 allowed
10 minbefore a code expires, 1 to 30 allowed
5wrong tries per code
5 / 10 mincodes per recipient, 30 s apart

Make the email yours

Send email codes from your own address with your own subject, or with a template your team designs in the console. SMS codes can use your approved sender ID.

Options
await notix.verify.send({
  to: "maria@example.com",
  appName: "Acme",
  codeLength: 8,          // 4 to 8 digits
  expiresIn: 300,         // 60 to 1800 seconds
  from: "Acme <security@acme.com>",
  templateId: "sign-in-code", // uses {{code}}, {{appName}}, {{expiresMinutes}}
});

Checked against a hash

The check uses a keyed hash of the code and of the client IP. A code in a sent email is removed after delivery, and SMS text is never stored.

Paid from your wallet

SMS codes are billed like any text, in naira, shillings or dollars, with no paid plan needed.

Know when a text lands

SMS codes report delivery back, so verification.delivered tells you the code reached the phone.

FAQ

Questions, answered

Can codes go by SMS and by email?
Yes. Email is the default. Pass channel: "sms" and a phone number with its country code to send a text instead. SMS codes are paid from your wallet like any text and need an appName.
What does the risk check look at?
Before anything is sent, Notix scores the request. For email it looks for disposable domains, domains with no mail server, role addresses like info@, repeated requests for one address or from one IP, and failed checks. For SMS it also checks the country, the number type and how often the number was texted.
What happens to a refused request?
Nothing is sent and no code exists. The API answers 422 RISK_REFUSED with the score and the signals that fired, so you can show the user a useful message.
How long does a code last, and how many tries are allowed?
By default a code has 6 digits and lasts 10 minutes. You can choose 4 to 8 digits and 60 to 1,800 seconds. Each code allows 5 wrong tries, and one recipient can get 5 codes every 10 minutes, at least 30 seconds apart.
Does Notix store the codes?
The verification record keeps only a keyed hash of each code, and a keyed hash of the user's IP address when you pass clientIp. An email code is also in our copy of the sent email for a short time, and is removed from it once the email is delivered. The text of an SMS is never stored. No API answer ever contains a code, and phone numbers are masked to their last four digits.
Can I use my own email design?
Yes. For email codes, set from and subject, or pass a templateId whose content uses {{code}}, {{appName}} and {{expiresMinutes}}.

Send your first email in five minutes.

Verify a domain, copy an API key, make one call. The free plan does not ask for a card.