Integration Guides

Step-by-step recipes for the most common Veriadd integration patterns — from CBN KYC tiers to lending fraud prevention.

Note:Code samples use the upcoming @veriadd/js client for readability. Every call maps 1:1 to the REST endpoints in the API Reference — integrate over HTTP today, swap in the SDK when published.

CBN KYC Tier Integration

The CBN requires Nigerian fintechs to collect and verify customer addresses at Tier 2 and above. Veriadd maps directly onto this flow.

CBN TierRequired checkVeriadd level
Tier 1Phone number only—
Tier 2BVN + address verification (typical)L2 (₦30)
Tier 3Enhanced due diligence incl. addressL3 (₦50)

Recommended flow

kyc_tier2.ts
typescript
import { VeriaClient } from "@veriadd/js";

const veria = new VeriaClient({ apiKey: process.env.VERIADD_KEY! });

async function upgradeTier2(userId: string, input: {
  postcode: string;
  state:    string;
  bvn:      string;
  phone:    string;
  firstName: string;
  lastName:  string;
}) {
  const result = await veria.verifyAddress({
    postcode:   input.postcode,
    state:      input.state,
    first_name: input.firstName,
    last_name:  input.lastName,
    bvn:        input.bvn,
    phone:      input.phone,
    level:      2,   // L2 for Tier 2 KYC
  });

  await db.users.update(userId, {
    kyc_tier:       result.status === "verified" ? 2 : 1,
    kyc_confidence: result.confidence,
    kyc_audit_id:   result.audit_id,     // store for CBN audit trail
    kyc_reasons:    result.reasons,
  });

  return result;
}
Note:Audit trail: Store audit_id against the customer record. Every decision keeps its confidence score, reasons and hashed identifiers — contact support for examiner retrieval requests.

Lending Fraud Prevention

Address fraud is a major driver of first-payment default in Nigerian consumer lending. Three common patterns:

  • •Synthetic identities — real BVN + fabricated address. Caught by state/LGA mismatch scoring.
  • •Rented addresses — legitimate postcode belongs to someone else. Caught by name-match failure.
  • •Borrowed phones — number belongs to someone else. Caught by phone-identity name matching.

Risk scoring pattern

fraud_gate.ts
typescript
import { VeriaClient } from "@veriadd/js";

const CONFIDENCE_THRESHOLD = 80;   // tune per portfolio risk appetite
// Match against the real reason strings returned in reasons[]
const FRAUD_CODES = [
  "does NOT match registry",
  "BVN/name mismatch",
  "phone identity",
];

async function lendingFraudGate(application: LoanApplication) {
  const result = await veria.verifyAddress({
    postcode:   application.postcode,
    state:      application.state,
    first_name: application.firstName,
    last_name:  application.lastName,
    bvn:        application.bvn,
    phone:      application.phone,
    level:      3,   // L3 for full audit + building status
  });

  const riskFlags = result.reasons.filter((r) =>
    FRAUD_CODES.some((code) => r.includes(code))
  );

  if (result.status === "invalid") {
    return { decision: "REJECT", reason: "invalid_postcode" };
  }

  if (result.confidence < CONFIDENCE_THRESHOLD || riskFlags.length > 0) {
    return {
      decision:  "MANUAL_REVIEW",
      flags:     riskFlags,
      auditId:   result.audit_id,
    };
  }

  return { decision: "APPROVE", auditId: result.audit_id };
}
Warning:Never store raw BVN or NIN values in your database. Veriadd hashes them (SHA-256) before storage. Your system should do the same with any PII collected during onboarding.

Webhooks & Async Callbacks

Veriadd verification is synchronous (~200ms), so webhooks are typically not required. However, for high-volume batch workflows you may want to decouple submission from result processing.

Recommended async pattern

worker.ts
typescript
// 1. Enqueue verification jobs
await queue.add("verify", {
  userId:   user.id,
  postcode: application.postcode,
  bvn:      application.bvn,
});

// 2. Worker processes jobs
queue.process("verify", async (job) => {
  const result = await veria.verifyAddress(job.data);
  await db.kycResults.insert({
    userId:    job.data.userId,
    auditId:   result.audit_id,
    status:    result.status,
    confidence: result.confidence,
  });

  // Notify downstream via your own webhook / event bus
  await events.emit("kyc.completed", { userId: job.data.userId, result });
});

Wallet Top-up Flow

Top up your Veriadd wallet via Paystack. This flow works for both server-side and client-initiated checkouts.

topup_flow.ts
typescript
// Step 1 — Initialize a Paystack checkout session
const topup = await veria.topupInit(
  50000 * 100,   // ₦50,000 in kobo
  "billing@myfintech.ng",
  "https://myfintech.ng/wallet/callback"
);

// Step 2 — Redirect user (or open popup)
window.location.href = topup.authorization_url;

// Step 3 — On callback, verify and credit wallet
//   GET /v1/wallet/topup/verify?reference=<reference>
const credited = await veria.topupVerify(topup.reference);
console.log(`Credited ₦${credited.balance_ngn}`);

Error Handling Best Practices

Retry strategy

retry.ts
typescript
import { VeriaClient, VeriaError } from "@veriadd/js";

const RETRYABLE = new Set(["rate_limited", "provider_unavailable", "nipost_rate_limited"]);
const MAX_RETRIES = 3;

async function verifyWithRetry(payload: VerifyRequest) {
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
    try {
      return await client.verifyAddress(payload);
    } catch (err) {
      if (err instanceof VeriaError && RETRYABLE.has(err.code)) {
        // Exponential back-off: 1s, 2s, 4s
        await sleep(1000 * 2 ** attempt);
        continue;
      }
      throw err;   // non-retryable — bubble up immediately
    }
  }
  throw new Error("Max retries exceeded");
}

Handling insufficient credits

credits.ts
typescript
try {
  const result = await client.verifyAddress({ ... });
} catch (err) {
  if (err instanceof VeriaError && err.code === "insufficient_credits") {
    // Pause onboarding; trigger wallet top-up flow
    await notifyOpsTeam("Veriadd wallet low");
    return { error: "service_unavailable", retryAfter: "topup_required" };
  }
  throw err;
}

NIPOST Postcode Format

Nigerian postcodes follow a 5-segment format: SS-DD-WWW-LL-UU where:

SSState code (2 letters, e.g. LA = Lagos)
DDDistrict number (2 digits)
WWWWard code (letter + 2 digits)
LLLocality code (2 letters)
UUUnit number (2 digits)

The API accepts minor formatting variations (extra spaces, lowercase) and returns a canonical form in postcode_canonical.