Consent
Ask a data subject to approve a check by link, email or USSD, then use the granted consent to unlock records such as the full BVN.
Every check needs a lawful basis. For most methods you attest that you obtained consent (consent: { "obtained": true }), or the method carries its own proof (a live selfie, a NINAuth share code). Some checks need more: the full BVN record (method: "core") is released only after the data subject approves the request themselves.
Speedmap's consent service handles that approval for you. In live mode, BVN consent requests are registered with the NIBSS Consent Hub; the subject can approve on any phone, including by USSD without a smartphone.
How it works
- You create a consent request for an identifier, with a purpose code and the categories of data you need.
- You share the
authorization_url(or the USSD code) with the subject, or Speedmap emails it. - The subject reviews who is asking and why, and approves with a one-time code, or declines.
- Speedmap sends
consent.updatedto your webhook. The grant is valid for 24 hours. - You call the product with
consent_id.
Create a consent request
Body parameters
identifier_typestringrequiredbvnornin.identifierstringrequiredThe 11-digit BVN or NIN.
categoriesstringY/N mask over four data categories, in order: personal identification, contact information, demographic details, geographic details. At least one
Y. Defaults toYYNN.channelstringredirect(you send the subject toauthorization_url),email(Speedmap emails the link) orussd(a USSD code is issued). Defaults toredirect.reasonstringOne of the 38 purpose codes. Defaults to
financialProducts.emailstringRequired when
channelisemail.redirect_urlstringhttp(s) URL to send the subject back to after they decide.
curl -X POST "http://localhost:4000/v1/consents" \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"identifier_type": "bvn",
"identifier": "22222222221",
"categories": "YYNN",
"channel": "ussd",
"reason": "financialProducts"
}'{
"id": "cns_423a8f143e7f2f8ffe7dda3344d36710",
"object": "consent",
"identifier_type": "bvn",
"subject": "*******2221",
"categories": "YYNN",
"category_labels": [
"Personal identification",
"Contact information"
],
"channel": "ussd",
"status": "pending",
"reason": "financialProducts",
"authorization_url": "https://speedmap-delta.vercel.app/consent/cns_423a8f143e7f2f8ffe7dda3344d36710",
"ussd_code": "*565*1*7*70205378#",
"redirect_url": null,
"granted_at": null,
"expires_at": "2026-10-20T09:41:07.512Z",
"created_at": "2026-10-19T09:41:07.512Z",
"mode": "sandbox"
}YNYN categories
categories is a four-letter mask. Each position is Y (requested) or N:
| Position | Category | Example |
|---|---|---|
| 1 | Personal identification | names, date of birth, photo |
| 2 | Contact information | phone, email |
| 3 | Demographic details | gender, marital status, nationality |
| 4 | Geographic details | address, state and LGA |
YYNN asks for identification and contact data only. Ask for the minimum you need: the subject sees exactly these categories, as category_labels.
Channels
- redirect: send the subject to
authorization_url, the hosted consent page at/consent/{id}. Addredirect_urlto bring them back to your app afterwards. - email: Speedmap emails the link to
email. - ussd: the response includes a
ussd_codesuch as*565*1*7*70205378#. The subject dials it on any phone, including a feature phone, to approve.
The hosted consent page
authorization_url opens a Speedmap-hosted page that shows your organization's name, the purpose, the categories requested and the masked identifier. The subject approves with a one-time code or declines. The consent id in the URL is unguessable (128 bits) and acts as the capability for the page, so share it only with the subject.
- A pending request expires after 24 hours.
- Five wrong one-time codes lock the request; create a new one.
- In the sandbox the one-time code is always
123456, and you can decide throughPOST /v1/sandbox/consents/{id}/approveor/deny. See Sandbox.
Use the consent
Once status is granted, pass the id to the product. For the full BVN record:
curl -X POST http://localhost:4000/v1/verify/bvn \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"method": "core",
"bvn": "22222222221",
"reason": "financialProducts",
"consent_id": "cns_423a8f143e7f2f8ffe7dda3344d36710"
}'The check fails with 412 when the consent cannot be used:
| Code | Meaning |
|---|---|
consent_not_granted | The consent is pending, denied, expired or revoked. |
consent_expired | The grant is older than 24 hours. Create a new request. |
consent_subject_mismatch | The consent was granted for a different identifier. |
An unknown consent_id returns 404 consent_not_found.
Retrieve and list
Lists take limit (up to 100, default 50) and the before cursor, like every other list. See Pagination.
Revoke a consent
Revoke a granted consent as soon as you no longer need it, or when the subject withdraws it. Revoked consents can no longer unlock checks.
curl -X POST "http://localhost:4000/v1/consents/cns_423a8f143e7f2f8ffe7dda3344d36710/revoke" \
-H "Authorization: Bearer sk_test_..."Only granted consents can be revoked; anything else returns 409 consent_not_granted.
Statuses and webhooks
| Status | Meaning |
|---|---|
pending | Waiting for the subject. Expires 24 hours after creation. |
granted | Approved. Usable for 24 hours from granted_at. |
denied | The subject declined. |
expired | Not decided in time, or locked after five wrong codes. |
revoked | Withdrawn by you. |
Every decision fires consent.updated with the consent object as data. Deciding on a request that is no longer pending returns 409 consent_not_pending.
Live availability
Live consent requests are available for BVN through the NIBSS Consent Hub. If the Hub rejects a request you receive 502 consent_hub_error. Live consent for NIN is not available yet; use the in_person or share_code NIN methods, which carry their own consent.
Questions? Email developers@speedmap.ng. This page is also available as Markdown.