DEVELOPER DOCUMENTATION

Integrate trust into every customer journey.

Create a verification flow, redirect customers securely, and receive a trusted outcome through your webhook.

Hosted verification integration

Create each customer journey securely.

Your backend creates a short-lived verification session with its API key. Your browser receives only a one-use launch URL; Ndini sends the authoritative result to your signed webhook.

1. Create a verification session

Create an application API key in the Dashboard and save the raw key when it is shown. For every customer attempt, your backend must create a new one-time browser session.

Server-side only: never place the API key in a URL, browser script, mobile deep link, HTML, or log. Send it only in the HTTPS Authorization header from your backend.

POST /api/verification-sessions
curl -X POST https://ndini.me/api/verification-sessions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "return_url": "https://client.example/kyc/complete?state=OPAQUE_REFERENCE"
  }'

return_url is optional. When supplied, it must be an absolute HTTPS URL without embedded credentials. Use an opaque, non-sensitive state value if your application needs to correlate the returning browser with its own customer record.

201 RESPONSE
{
  "verification_url": "https://ndini.me/r/ONE_TIME_LAUNCH_CODE",
  "session_id": "9ecf...",
  "expires_at": "2026-09-05T12:10:00Z"
}
StatusMeaning
201Session created.
400Invalid return_url.
401Missing, invalid, inactive, or malformed Bearer API key.
429Monthly quota or endpoint rate limit exceeded.

2. Redirect the customer

Return the newly created verification_url to your frontend, then navigate the customer to it. Generate a fresh session for every attempt; never construct or reuse a launch URL.

  1. Ndini validates the one-time launch code and immediately consumes it.
  2. Ndini creates an HttpOnly, Secure browser cookie and redirects to /verify.
  3. The customer completes document capture, a guided presence check, and facial identity confirmation.
  4. The verification request is also one-use and remains linked to the API-key owner and application.

The launch URL expires after the configured session lifetime (10 minutes by default). An expired or reused URL returns 410 Gone.

3. Handle browser completion

When the customer selects Continue, Ndini redirects to the stored return_url and preserves its existing query parameters. It adds:

https://client.example/kyc/complete
  ?state=OPAQUE_REFERENCE
  &status=success
  &verified=true
  &decision=verified

When screening requires human review, the callback uses status=review, verified=false, and decision=review. Always use the signed webhook as the authoritative result.

Do not trust browser query parameters as proof of identity. They are navigation hints and can be changed by the user. Grant access only after receiving and validating the signed verification.completed webhook.

4. Receive and verify the result

Configure the application webhook URL and API version on the token in the Dashboard. Ndini posts the result after verification and records it against that token owner and application.

The webhook must use HTTPS, resolve to a public network address, accept POST, and return 200 OK. Ndini does not follow redirects.

Verify the signature before processing

Every delivery includes X-Ndini-Signature: sha256=<hex-digest>. Compute HMAC-SHA256 over the exact raw request body using the separate webhook signing secret shown when the token is created or rotated, then compare signatures in constant time.

expected = "sha256=" + HMAC_SHA256(webhook_secret, raw_request_body)
constant_time_compare(expected, request.headers["X-Ndini-Signature"])

5. Pulse Check Requirement

Before Ndini creates a token, it validates your webhook by sending a POST request with event: "pulse". Your webhook must accept this event and return HTTP 200.

REQUEST POST /your-webhook-endpoint
{
  "event": "pulse",
  "timestamp": "2026-05-06T10:30:00Z"
}
EXPECTED RESPONSE HTTP 200
{
  "ok": true,
  "event": "pulse"
}

6. Webhook Payload Schemas

Webhook payloads are versioned and differ between v1 and v2.

v1 webhook payload

{
  "event": "verification.completed",
  "timestamp": "2026-05-26T10:30:00+00:00",
  "data": {
    "application": "Bank Account Opening",
    "id": "63-1234567 Z 32",
    "dob": "1990-01-01",
    "date_of_issue": "2015-05-20",
    "full_name": "JOHN DOE",
    "face_match": {
      "verified": false,
      "similarity_score": 0.6079,
      "error": null
    },
    "hologram_check": {
      "passed": true
    },
    "liveness_phase1": {
      "passed": true
    }
  },
  "id": "63-1234567 Z 32"
}

v2 webhook payload

{
  "event": "verification.completed",
  "timestamp": "2026-05-26T10:30:00+00:00",
  "application": "Bank Account Opening",
  "attempt_id": "atm_12345",
  "verification_status": "review",
  "id": "63-1234567 Z 32",
  "dob": "1990-01-01",
  "date_of_issue": "2015-05-20",
  "full_name": "JOHN DOE",
  "face_match": {
    "verified": false,
    "similarity_score": 0.6079,
    "error": null
  },
  "face_decision": {
    "status": "fail",
    "verified": false
  },
  "pep_screening": {
    "status": "complete",
    "decision": "potential_match",
    "candidate_count": 1,
    "match_count": 1,
    "candidates": [
      {
        "reference": "Q123",
        "name": "EXAMPLE PERSON",
        "categories": ["Politically exposed person"]
      }
    ],
    "checked_at": "2026-09-05T12:54:33+00:00"
  },
  "hologram_check": {
    "passed": true
  },
  "liveness_phase1": {
    "passed": true,
    "reason": null
  }
}

Identity decision

face_decision.status reports whether the customer’s facial identity confirmation succeeded. Use the overall verification_status when making your final workflow decision.

Identity details

The webhook includes the customer’s verified identity details, including full_name, dob, and date_of_issue.

PEP screening

pep_screening.decision is clear, potential_match, review, or not_screened. A potential match is a review signal, not proof that the customer is a politically exposed person. If screening cannot be completed safely, the decision is review.