Dashboard
https://api.1trustmfb.com/v1Download OpenAPI

1Trust API

The 1Trust API has two products. Direct debit collects recurring payments from any Nigerian bank account: link a customer's account once, then charge any amount up to the limit. Identity verifies a customer's BVN or NIN. Each is switched on separately for your business.

All requests go to https://api.1trustmfb.com/v1 over HTTPS. Requests and responses are JSON. Amounts are in kobo (₦1 = 100 kobo).

How direct debit works

  1. Your server calls POST /mandates. We register the mandate with the customer's bank.
  2. We email the customer: send ₦50 from the mandated account to activate.
  3. The bank approves the mandate and we send you mandate.active.
  4. You call POST /mandates/{code}/debit whenever a payment is due.
  5. We send debit.success or debit.failed.

Authentication

Authenticate every request with your secret key in the Authorization header. Find your keys under API keys & webhooks in the dashboard.

Test keys start with sk_test_ and never move money. Live keys start with sk_live_ and move real money. Keep secret keys on your server only.

Header
Authorization: Bearer sk_test_3b1f…c81e

Errors

Every response has a boolean status and a human-readable message. Failed requests also return an error.code you can branch on.

HTTPMeaning
200 / 201Request succeeded
400A field is missing or invalid
401Missing or wrong secret key
404Mandate or debit not found
409Duplicate reference, or a second debit on the same day
422The customer's bank declined the request (see error.code)
5xxSomething went wrong on our side. Retry with the same reference.
error.codeWhat to do
missing_fieldA required field is missing; error.field names it
invalid_phonePhone must be 11 digits, starting with 0
invalid_account_numberAccount number must be 10 digits
invalid_emailSend a valid email address
invalid_referenceUse 1–50 letters, numbers, underscores or hyphens
already_debited_todayOne debit per mandate per day. Try again tomorrow
customer_existsA customer with this email is already saved; fetch it instead
customer_id_takenYour customer ID is already used by another customer
live_not_enabledUse your test keys until 1Trust approves Live
merchant_suspendedContact 1Trust support
mandate_cooling_offFirst charge is allowed 6 hours after activation
amount_below_minimumMinimum debit is ₦500
mandate_not_activeWait for the mandate.active webhook before debiting
amount_exceeds_mandateDebit no more than the mandate amount
insufficient_fundsRetry on another day
account_name_mismatchCheck the account number and BVN belong to the same person
mandate_account_mismatchThe account or bank doesn't match the mandate
mandate_not_startedThe mandate's start date is in the future. Charge on or after the start date.
limit_exceededOn a debit: the customer's transfer limit or withdrawal frequency is reached. On mandate create: your daily mandate limit is reached; try tomorrow or contact 1Trust
status_unknownThe debit isn't confirmed yet. We check again and send a webhook; don't retry with a new reference
invalid_bvnBVN must be 11 digits and exist
invalid_nin_sharecodeAsk the customer for a fresh ShareCode from the NINAuth app
consent_declinedThe customer said no. Start a new verification if they change their mind
verification_unavailableTemporarily unavailable. Retry in a few minutes; you aren't billed
consent_requiredA BVN Full check needs the customer's consent. Request consent, or pass a consent_code
sandbox_limit_reachedTest mode only: the allowance of test checks against the real verification service is used up. Contact 1Trust
invalid_dateSend dates as YYYY-MM-DD (Lagos days, both ends included), with from on or before to
idempotency_key_reusedThis Idempotency-Key was used with a different request. Use a new key
idempotency_in_progressThe first request with this key is still running. Retry shortly with the same key
delivery_in_progressThe webhook delivery is being sent right now. Check its status in a minute
conflictThe request clashed with another one at the same moment. Retry
rate_limitedToo many requests (for example, more than 10 test webhooks a minute). Wait and retry
module_not_enabledThis product isn't switched on for your business. Ask 1Trust to enable it
Error response
{
  "status": false,
  "message": "Mandate is not active yet",
  "error": {
    "code": "mandate_not_active",
    "field": null
  }
}

Pagination

List endpoints return 50 records per page by default. Pass page and per_page (up to 100). The response includes a meta object so you know when to stop.

meta
{
  "status": true,
  "message": "Mandates retrieved",
  "data": [
    "…"
  ],
  "meta": {
    "total": 236,
    "page": 2,
    "per_page": 50,
    "page_count": 5
  }
}

Rate limits

Each secret key can make up to 20 requests per second. Above that you get HTTP 429; wait a second and retry. Separately, each mandate can be debited once per day.

Test mode

Use test keys to build your integration. Test mandates move to pending_activation straight away. Use these account numbers to trigger outcomes:

Account numberResult
0000000001Mandate activates after 60 seconds
0000000002Mandate rejected: account name mismatch
0000000003Debits fail with insufficient_funds
Any otherStays pending until you choose Simulate ₦50 transfer in the dashboard
GET/customers

List customers

Returns your customers, newest first, with how many mandates each has, what you have collected from them and their latest identity check.

Query

qstringOptional

Search name, email, phone or your customer ID

page, per_pageintegerOptional

Defaults 1 and 50; max 100

Request
curl https://api.1trustmfb.com/v1/customers?q=zainab \
  -H "Authorization: Bearer sk_test_3b1f…c81e"
Response
{
  "status": true,
  "message": "Customers retrieved",
  "data": [
    {
      "id": "cmuszz1wv000569urq2kfw9de",
      "customer_id": "CUST-00931",
      "name": "Zainab Abdullahi",
      "email": "zainab.abdullahi@example.com",
      "phone": "08023456789",
      "address": "22 Gana Street, Maitama, Abuja",
      "mandates": 1,
      "active_mandates": 1,
      "collected": 4500000,
      "identity": {
        "status": "verified",
        "checked_at": "2026-10-01T08: 00: 02+01: 00"
      },
      "created_at": "2026-09-25T11: 30: 00+01: 00"
    }
  ],
  "meta": {
    "total": 1,
    "page": 1,
    "per_page": 50,
    "page_count": 1
  }
}
POST/customers

Create customer

Saves a customer so you can pick them when you create a mandate. Each email, and each of your customer IDs, can be used once per mode.

Body

namestringRequired

Full name as on the bank account

emailstringRequired

Customer email

phonestringRequired

11-digit Nigerian phone number

addressstringRequired

Customer address

customer_idstringOptional

Your ID for this customer, up to 40 characters

Errors

409 customer_exists when the email is already saved, 409 customer_id_taken when your customer ID is already used.

Request
curl -X POST https://api.1trustmfb.com/v1/customers \
  -H "Authorization: Bearer sk_test_3b1f…c81e" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Zainab Abdullahi",
  "email": "zainab.abdullahi@example.com",
  "phone": "08023456789",
  "address": "22 Gana Street, Maitama, Abuja",
  "customer_id": "CUST-00931"
}'
Response
{
  "status": true,
  "message": "Customer created",
  "data": {
    "id": "cmuszz1wv000569urq2kfw9de",
    "customer_id": "CUST-00931",
    "name": "Zainab Abdullahi",
    "email": "zainab.abdullahi@example.com",
    "phone": "08023456789",
    "address": "22 Gana Street, Maitama, Abuja",
    "mandates": 0,
    "active_mandates": 0,
    "collected": 0,
    "identity": null,
    "created_at": "2026-09-25T11: 30: 00+01: 00"
  }
}
GET/banks

List banks

Returns banks whose customers can be debited by direct debit. Use the code as bank_code when creating a mandate.

Request
curl https://api.1trustmfb.com/v1/banks \
  -H "Authorization: Bearer sk_test_3b1f…c81e"
Response
{
  "status": true,
  "message": "Banks retrieved",
  "data": [
    {
      "code": "044",
      "name": "Access Bank"
    },
    {
      "code": "058",
      "name": "GTBank"
    },
    {
      "code": "50515",
      "name": "Moniepoint MFB"
    }
  ]
}
POST/mandates

Create mandate

Registers the mandate and sends the customer activation instructions by email. The mandate starts as pending_activation. It becomes active once the customer sends ₦50 from the mandated account, usually within 2 hours.

Body

customer.namestringRequired

Account holder's full name

customer.emailstringRequired

Where we send activation instructions

customer.phonestringRequired

11-digit Nigerian phone number

customer.addressstringRequired

Account holder's address

customer.idstringOptional

Your ID for this customer

bank_codestringRequired

From List banks

account_numberstringRequired

10-digit NUBAN

max_amountintegerRequired

Most you can debit at once, in kobo

end_datedateRequired

YYYY-MM-DD. Mandate stops after this date.

descriptionstringOptional

What the customer is paying for. Shown on their activation message.

referencestringOptional

Your unique ID for this mandate

Request
curl -X POST https://api.1trustmfb.com/v1/mandates \
  -H "Authorization: Bearer sk_test_3b1f…c81e" \
  -H "Content-Type: application/json" \
  -d '{
  "customer": {
    "name": "Zainab Abdullahi",
    "email": "zainab.abdullahi@example.com",
    "phone": "08023456789",
    "address": "22 Gana Street, Maitama, Abuja",
    "id": "CUST-00931"
  },
  "bank_code": "058",
  "account_number": "0145678923",
  "max_amount": 4500000,
  "end_date": "2027-09-30",
  "description": "Loan repayment",
  "reference": "MND-REF-00931"
}'
Response
{
  "status": true,
  "message": "Mandate created. Awaiting customer activation.",
  "data": {
    "mandate_code": "MND_1052X7QA",
    "mandate_reference": "9516439/1/5218796181",
    "status": "pending_activation",
    "customer": {
      "name": "Zainab Abdullahi",
      "email": "zainab.abdullahi@example.com",
      "phone": "08023456789"
    },
    "bank_code": "058",
    "account_number": "0145678923",
    "account_name": "ZAINAB ABDULLAHI",
    "max_amount": 4500000,
    "description": "Loan repayment",
    "start_date": "2026-09-25",
    "end_date": "2027-09-30",
    "activation": {
      "amount": 5000,
      "account_number": "9880218357",
      "bank_name": "Titan-Paystack",
      "instructions": "Pay ₦50 from account 0145678923 to 9880218357 (Titan-Paystack) to activate your mandate.",
      "expires_at": "2026-10-02T10: 00: 00+01: 00"
    },
    "reference": "MND-REF-00931",
    "created_at": "2026-09-25T10: 00: 00+01: 00"
  }
}
GET/mandates

List mandates

Returns your mandates, newest first.

Query

statusstringOptional

pending_activation, active, cancelled, expired or rejected

emailstringOptional

Filter by customer email

from, todateOptional

Created between these dates

page, per_pageintegerOptional

Defaults 1 and 50; max 100

Request
curl https://api.1trustmfb.com/v1/mandates?status=active&per_page=20 \
  -H "Authorization: Bearer sk_test_3b1f…c81e"
GET/mandates/{mandate_code}

Fetch mandate

Returns one mandate with its current status and, if still pending, the activation instructions.

Mandate statuses

StatusMeaning
pending_activationRegistered. Waiting for the customer's ₦50 transfer.
activeReady to debit
rejectedBank declined, often a BVN or name mismatch
expiredNo ₦50 transfer within 7 days
cancelledStopped by you or the customer
completedEnd date passed
Request
curl https://api.1trustmfb.com/v1/mandates/MND_1052X7QA \
  -H "Authorization: Bearer sk_test_3b1f…c81e"
POST/mandates/{mandate_code}/pause

Pause or resume a mandate

Pausing suspends the mandate so no debits can go through. Call POST /mandates/{mandate_code}/resume to make it active again. Neither call needs a body.

Request
curl -X POST https://api.1trustmfb.com/v1/mandates/MND_1052X7QA/pause \
  -H "Authorization: Bearer sk_test_3b1f…c81e"
POST/mandates/{mandate_code}/cancel

Cancel mandate

Stops all future debits. This can't be undone. Create a new mandate to start again.

reasonstringOptional

Stored for your records

Request
curl -X POST https://api.1trustmfb.com/v1/mandates/MND_1052X7QA/cancel \
  -H "Authorization: Bearer sk_test_3b1f…c81e" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Customer closed account"
}'
POST/mandates/{mandate_code}/debit

Debit mandate

Charges an active mandate, at least 6 hours after it became active. Amount: ₦500 up to the mandate's max, one debit per mandate per day. We deduct ₦107.50 (₦100 + VAT) and pay you the rest at 3:00pm the same day (debits before 2:00pm) or 9:00am the next business day. We confirm the final result with a debit.success or debit.failed webhook.

amountintegerRequired

In kobo, up to the mandate's max_amount. Any amount, any day.

referencestringRequired

Your unique reference. Reuse it to retry safely.

narrationstringOptional

Up to 50 characters. Defaults to your business name + "via 1Trust".

Request
curl -X POST https://api.1trustmfb.com/v1/mandates/MND_1052X7QA/debit \
  -H "Authorization: Bearer sk_test_3b1f…c81e" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": 4500000,
  "reference": "ORD_7H2KD91Q",
  "narration": "October instalment"
}'
Response
{
  "status": true,
  "message": "Debit successful",
  "data": {
    "reference": "ORD_7H2KD91Q",
    "mandate_code": "MND_1052X7QA",
    "amount": 4500000,
    "fee": 10750,
    "net": 4489250,
    "status": "success",
    "narration": "October instalment",
    "session_id": "999999260925100455123456789012",
    "created_at": "2026-10-01T09: 12: 44+01: 00"
  }
}
POST/mandates/{mandate_code}/retry-activation

Retry activation

For a pending_activation mandate: resends the ₦50 instructions to the customer and rechecks its status. Pass mandate_codes to POST /mandates/retry-activation to retry many at once.

Request
curl -X POST https://api.1trustmfb.com/v1/mandates/MND_1052X7QA/retry-activation \
  -H "Authorization: Bearer sk_test_3b1f…c81e"
GET/debits/{reference}

Fetch debit

Returns a debit by your reference. Use it to confirm the result if you missed a webhook. List all debits with GET /debits, which takes the same filters as List mandates.

Request
curl https://api.1trustmfb.com/v1/debits/ORD_7H2KD91Q \
  -H "Authorization: Bearer sk_test_3b1f…c81e"
GET/payouts

List payouts

Two payouts each business day, minus fees: 3:00pm for debits made between 2:00am and 1:59pm, and 9:00am the next business day for the rest. Weekend debits are paid Monday 9:00am. Each payout lists the debits it covers.

Response
{
  "status": true,
  "message": "Payouts retrieved",
  "data": [
    {
      "id": "PO_20260926",
      "date": "2026-09-26",
      "debits": 3,
      "collected": 21500000,
      "fees": 32250,
      "amount": 21467750,
      "status": "paid",
      "account": {
        "bank_code": "058",
        "account_number": "0123456780"
      }
    }
  ]
}

Identity verification

Verify who a customer is with their BVN or NIN. Choose a check:

CheckWhat you sendWhat you get
BVN · FullBVNThe customer approves first (OTP, USSD or face). Then: name, date of birth, gender, phone and photo.
BVN · MatchBVN + the details you holdYes/no for each detail. No approval step.
NIN · FullNIN ShareCodeName, date of birth, gender, phone and photo. The ShareCode is the customer's consent.
NIN · MatchShareCode + the details you holdYes/no for each detail.

What your customer needs

CheckCustomer does
BVN · FullApproves when we ask (OTP, USSD or face) by the consent link. If they already approved and have a consent code, send it as consent_code and we verify at once.
NINOpens the NINAuth app (Play Store or App Store), signs in with their NIN and generates a 6-character ShareCode for you.

You are billed only for checks that return a result: verified, partial_match, or failed with details_do_not_match. A customer's approval for BVN · Full stays valid until consent_expires_at, so repeat checks don't ask again.

Test numbers

BVNResult
22222222222Verifies (Full: after you choose Simulate consent)
22222222223Customer declines consent
22222222224Match: phone doesn't match
Any test BVN + consent code 4878BF48BD6FD92CFull: verifies at once
ShareCode 000000NIN: fails with invalid_nin_sharecode; any other ShareCode verifies
POST/verifications

Start verification

For BVN · Full without a consent_code, the verification starts as awaiting_consent. With customer_present: true you get a consent_url to send the customer to; otherwise we email them a link. You get verification.verified when it completes. Every other check returns its result straight away.

Body

typestringRequired

bvn or nin

methodstringRequired

full or match

numberstringRequired

11-digit BVN, or the 6-character NIN ShareCode

customer.emailstringRequired

Where we send the consent link

customer.phonestringRequired

11-digit Nigerian phone number

customer_presentbooleanOptional

BVN · Full only. Default true

return_urlstringOptional

Where the customer lands after approving. Defaults to your setting

consent_codestringOptional

BVN · Full. The customer already approved and gave you a code: we skip the request and verify at once

reasonstringOptional

NIN only, required. Why you need the data, e.g. financialProducts, creditBackgroundCheck, insurance, employmentRecruitment. Default financialProducts

first_name, middle_name, last_namestringOptional

Required for Match

dobdateOptional

YYYY-MM-DD. Required for Match

genderstringOptional

male or female. Required for Match

Request
curl -X POST https://api.1trustmfb.com/v1/verifications \
  -H "Authorization: Bearer sk_test_3b1f…c81e" \
  -H "Content-Type: application/json" \
  -d '{
  "type": "bvn",
  "method": "full",
  "number": "22222222222",
  "customer": {
    "name": "Zainab Abdullahi",
    "email": "zainab.abdullahi@example.com",
    "phone": "08023456789"
  },
  "customer_present": true
}'
Response
{
  "status": true,
  "message": "Consent requested",
  "data": {
    "id": "VER_3112KQD",
    "type": "bvn",
    "method": "full",
    "status": "awaiting_consent",
    "customer": {
      "name": "Zainab Abdullahi",
      "email": "zainab.abdullahi@example.com",
      "phone": "08023456789"
    },
    "number_last4": "2222",
    "consent_url": "https://verify.1trustmfb.com/c/ver_3112kqd",
    "fee": null,
    "created_at": "2026-10-03T09: 12: 44+01: 00"
  }
}
GET/verifications/{id}

Fetch verification

Returns one verification with its result. photo_url is signed and expires after 15 minutes.

Statuses

StatusMeaning
awaiting_consentWaiting for the customer to approve
verifiedRecord found; all details match
partial_matchSome details don't match. See match
failedNo detail matched, or the number is invalid. See error.code
consent_declinedCustomer said no
expiredNo approval within 24 hours
Request
curl https://api.1trustmfb.com/v1/verifications/VER_3112KQD \
  -H "Authorization: Bearer sk_test_3b1f…c81e"
Response · Full
{
  "status": true,
  "message": "Verification retrieved",
  "data": {
    "id": "VER_3112KQD",
    "type": "bvn",
    "method": "full",
    "status": "verified",
    "result": {
      "first_name": "Zainab",
      "middle_name": "Hadiza",
      "last_name": "Abdullahi",
      "dob": "1993-04-18",
      "gender": "female",
      "phone": "08023456789",
      "photo_url": "https://files.1trustmfb.com/v/VER_3112KQD.jpg?exp=900"
    },
    "number_last4": "2222",
    "consent_expires_at": "2026-11-02T23: 59: 59+01: 00",
    "billable": true,
    "completed_at": "2026-10-03T09: 14: 10+01: 00"
  }
}
Response · Match
{
  "status": true,
  "message": "Verification complete",
  "data": {
    "id": "VER_3115PLM",
    "type": "bvn",
    "method": "match",
    "status": "partial_match",
    "match": {
      "first_name": true,
      "middle_name": true,
      "last_name": true,
      "dob": true,
      "gender": true,
      "phone": false
    },
    "billable": true
  }
}
GET/verifications

List verifications

Newest first.

Query

statusstringOptional

Any status above

typestringOptional

bvn or nin

from, todateOptional

Created between these dates

page, per_pageintegerOptional

Defaults 1 and 50; max 100

Request
curl https://api.1trustmfb.com/v1/verifications?status=verified&type=bvn \
  -H "Authorization: Bearer sk_test_3b1f…c81e"

Webhooks

We send a POST to your webhook URL whenever a mandate, debit or verification changes. Reply with HTTP 200 within 10 seconds. We retry failed deliveries up to 5 times over 24 hours.

EventSent when
mandate.createdMandate registered
mandate.activeCustomer sent ₦50. You can debit now.
mandate.rejectedBank declined the mandate
mandate.expiredNo activation within 7 days
mandate.cancelledMandate stopped
debit.successMoney collected
debit.failedDebit declined, with a reason
payout.paidSettlement sent to your bank account
verification.consent_grantedCustomer approved a BVN · Full check
verification.verifiedResult ready (verified or partial match)
verification.failedFailed, declined or expired
mandate.active
{
  "event": "mandate.active",
  "data": {
    "mandate_code": "MND_1052X7QA",
    "status": "active",
    "account_number": "0145678923",
    "bank_code": "058",
    "amount": 4500000,
    "reference": "CUST-00931",
    "activated_at": "2026-09-25T11: 42: 10+01: 00"
  }
}

Verify signatures

Every webhook carries an x-1trust-signature header: an HMAC-SHA512 of the raw request body, keyed with the webhook signing secret (starts with whsec_, shown once when you save the webhook URL). Compute it yourself and reject the request if it doesn't match.

To also reject replays, use x-1trust-signature-v2: the HMAC-SHA512 of {x-1trust-timestamp}.{raw body}. Reject it when the timestamp is more than 5 minutes old. Each event also has a stable x-1trust-event-id across retries, so you can ignore duplicates.

Verify
import crypto from "node:crypto";

app.post("/webhooks/1trust", express.raw({ type: "*/*" }), (req, res) => {
  const hash = crypto
    .createHmac("sha512", process.env.ONETRUST_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");
  if (hash !== req.headers["x-1trust-signature"]) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  // handle event.event, e.g. "debit.success"
  res.sendStatus(200);
});