Credpoint Developer API

Integrate deposit reconciliation into your platform.

Base URL: https://credpoint.net/api/v1

Amount units: All monetary values in the API are in major units (e.g., naira, not kobo). 100000 means ₦100,000.00 NGN.

Authentication

Every API request must include four headers. The signature is an HMAC-SHA256 of timestamp.nonce.json_payload signed with your API Secret.

Your integration has three secrets: api_key — sent in the X-API-Key header on every request. api_secret — used to sign API requests (HMAC-SHA256). webhook_secret — used to verify callback signatures. Different from the API secret.

Header Description
X-API-KeyYour integration API key.
X-TimestampCurrent Unix timestamp (within ±5 minutes of server time).
X-NonceUnique random string per request (16+ bytes hex recommended).
X-SignatureHMAC-SHA256 of timestamp.nonce.payload.
$payload   = json_encode($requestBody);
$message   = $timestamp . '.' . $nonce . '.' . $payload;
$signature = hash_hmac('sha256', $message, $apiSecret);

// Headers
X-API-Key:    sk_live_...
X-Timestamp:  $timestamp
X-Nonce:      $nonce
X-Signature:  $signature

Rate Limiting

Scope Limit
Global (per IP)60 requests / minute
Per API key (deposit initiation)10 requests / minute
Per API key (status query)30 requests / minute
Inbound email webhook100 requests / minute

When rate limited, the API returns 429 Too Many Requests with a Retry-After header.

Initiate Deposit

Creates a deposit intent and returns a virtual bank account for the customer to pay into. The intent expires after 3 hours if no matching bank alert is received.

POST /deposits/initiate

Request Body

Field Type Required Description
external_systemstringyesYour platform/system identifier (a-z, 0-9, _, -).
external_referencestringyesUnique transaction reference in your system. Must be unique per deposit.
user_idstringyesCustomer ID in your platform.
user_full_namestringyesCustomer full name (letters, spaces, ., -, ').
expected_amountdecimalyesAmount to deposit (excluding fee). Max: ₦10,000,000 or integration limit.
currencystringno3-letter ISO currency (default: NGN, uppercase).
user_identifierstringnoAdditional customer identifier (email, phone, etc.).
callback_urlurlyesHTTPS URL to receive status callbacks. Domain must be whitelisted.

Example Request

POST /api/v1/deposits/initiate HTTP/1.1
Host: credpoint.net
Content-Type: application/json
X-API-Key: sk_live_b67lhLmfa5gtksGVqI9IQLiDgZUNyRqlJM5tsyhn
X-Timestamp: 1757049600
X-Nonce: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
X-Signature: 3f5b9c8...

{
  "external_system": "skymove",
  "external_reference": "skymove_txn_12345",
  "user_id": "user_001",
  "user_full_name": "John Doe",
  "expected_amount": 100000,
  "currency": "NGN",
  "callback_url": "https://skymoveapp.com/api/v1/credpoint/callback"
}

Success Response — 200 OK

{
  "success": true,
  "data": {
    "credpoint_reference": "CP-53000",
    "match_code": "hot tea 42",
    "status": "pending",
    "amount": 100000,
    "fee": 200,
    "total_amount": 100200,
    "currency": "NGN",
    "bank_account": {
      "bank_name": "GLOBUS BANK",
      "account_number": "1000509333",
      "account_name": "SKY-MOVE GLOBAL LTD"
    },
    "expires_at": "2026-09-04T03:47:51+01:00",
    "min_wait_time": 180,
    "max_wait_time": 3600
  },
  "message": "Deposit intent registered successfully. Customer should transfer the total amount to the provided bank account and include the match_code in the narration."
}

The customer must transfer exactly total_amount to the provided bank account. Once received and matched against a bank alert, the deposit will be approved and a callback sent to your callback_url.

Match Code

The match_code is a unique, human-readable code (e.g., "hot tea 42") generated per deposit intent. Ask the customer to include this code in their transfer narration. When the code is found in the bank alert narration, it bypasses name similarity checks and awards +40 match score points — significantly improving auto-approval rates for transfers where the sender name doesn't match the registered customer name.

Idempotent Re-initiation

If you call POST /deposits/initiate with an external_reference that already exists for your integration, Credpoint returns the original intent data (with status reflecting the current state) instead of creating a duplicate. This means you can safely retry after a network timeout — you will always get back the original intent's bank account details and match code.

Security Notes

  • Nonce replay protection: Each nonce is cached for 10 minutes. Reusing a nonce within that window returns INVALID_NONCE.
  • Timestamp window: The timestamp must be within ±5 minutes of server time.
  • Callback domain whitelist: The callback_url domain must be whitelisted in Credpoint's config. Subdomains of whitelisted domains are automatically allowed (e.g., staging.api.yourplatform.com is allowed if yourplatform.com is whitelisted). Contact support to add your domain.

Fee Structure

Credpoint charges a tiered transaction fee on each deposit. The fee is added to the deposit amount — the customer must transfer expected_amount + fee to the bank account.

Amount Range (NGN) Fee (NGN)
₦0 – ₦5,000₦50
₦5,001 – ₦50,000₦100
₦50,001+₦200

Fee cap: ₦1,000 (configurable). Fees may vary per integration based on custom fee configurations. The API response always includes the exact fee and total_amount for the given deposit.

Error Codes

Code HTTP Meaning
MISSING_HEADERS401One or more auth headers are missing.
INVALID_API_KEY401Unknown or inactive API key.
INVALID_NONCE401Nonce was already used.
INVALID_SIGNATURE401HMAC signature does not match.
INVALID_TIMESTAMP401Timestamp is more than 5 minutes off.
INTEGRATION_INACTIVE403Integration is suspended or inactive.
VALIDATION_ERROR400Request body failed validation.
AMOUNT_EXCEEDS_MAX400Deposit exceeds integration max deposit amount.
INVALID_CALLBACK_URL400Callback URL must use HTTPS in production.
INVALID_CALLBACK_DOMAIN400Callback domain is not whitelisted.
NO_BANK_ACCOUNT503No active bank account available for this amount.

Callbacks (Webhooks)

When a deposit reaches a terminal state (approved, rejected, or expired), Credpoint sends a POST request to your callback_url with the deposit details.

Callback statuses: Credpoint sends callbacks for approved, rejected, and expired deposits. Integrators should check the status field and decide how to handle each type. If your integration only processes approvals, you can safely ignore rejected and expired callbacks.

Callback Headers

Header Description
X-SignatureHMAC-SHA256 of the JSON payload body, signed with your webhook_secret (not the API secret).
X-Credpoint-ReferenceCredpoint's internal reference (e.g., CP-53000).
Content-TypeAlways application/json.

Callback Payload

The payload structure varies by status. Approved callbacks include full match details; rejected and expired callbacks include a lighter payload.

Approved Callback

{
  "external_reference": "skymove_txn_12345",
  "status": "approved",
  "amount": 100000,
  "currency": "NGN",
  "approved_at": "2026-09-04T03:50:00+01:00",
  "credpoint_reference": "CP-53000",
  "idempotency_key": "a1b2c3d4e5f6...",
  "match_score": 92,
  "bank_reference": "TRX123456789",
  "bank_alert_id": 1234,
  "fee_paid": true,
  "amount_paid": 100200
}

Rejected / Expired Callback

{
  "external_reference": "skymove_txn_12345",
  "status": "rejected",
  "currency": "NGN",
  "credpoint_reference": "CP-53000",
  "idempotency_key": "a1b2c3d4e5f6...",
  "expected_amount": 100000,
  "expires_at": "2026-09-04T06:47:51+01:00"
}

The status field will be "rejected" (admin manually rejected) or "expired" (3-hour timeout with no matching bank alert).

Payload Fields

Field Type Description
external_referencestringThe reference you provided when initiating the deposit. All statuses.
statusstring"approved", "rejected", or "expired". All statuses.
currencystring3-letter ISO currency code. All statuses.
credpoint_referencestringCredpoint's internal reference (CP-{id}). All statuses.
idempotency_keystringSHA-256 hash for duplicate prevention. All statuses.
expected_amountfloatOriginal expected deposit amount (major units). Rejected/expired only.
expires_atstringISO 8601 expiry timestamp. Rejected/expired only.
amountfloatAmount to credit to the user's wallet (major units). May differ from expected_amount if the fee wasn't fully paid. Approved only.
approved_atstringISO 8601 timestamp of approval. Approved only.
match_scoreint|nullMatch confidence score (0–100). May be null. Approved only.
bank_referencestring|nullBank transaction reference from the alert. May be null. Approved only.
bank_alert_idintInternal Credpoint bank alert ID. Approved only.
fee_paidboolWhether the customer paid the transaction fee. If false, amount will be less than expected_amount. Defaults to true. Approved only.
amount_paidfloatActual amount the customer transferred (major units). Approved only.

Verifying the Callback

// On your callback endpoint
$payload   = file_get_contents('php://input');  // raw JSON body
$signature = $_SERVER['HTTP_X_SIGNATURE'];     // X-Signature header
$webhookSecret = 'whsec_your_webhook_secret';   // NOT the API secret

$expectedSignature = hash_hmac('sha256', $payload, $webhookSecret);

if (!hash_equals($signature, $expectedSignature)) {
    http_response_code(401);
    exit('Invalid signature');
}

$data = json_decode($payload, true);

// Use idempotency_key to prevent duplicate processing
$deposit = Deposit::firstOrCreate(
    ['idempotency_key' => $data['idempotency_key']],
    [
        'external_reference' => $data['external_reference'],
        'amount'             => $data['amount'],
        'status'             => $data['status'],
        'approved_at'        => $data['approved_at'],
    ]
);

Signature pitfall: The signature is computed over the raw JSON body string (exactly as received), not a re-serialized version. Do not json_decode then json_encode and expect the same signature — PHP's json_encode escapes forward slashes (\/) and may reorder keys. Always verify against the raw php://input or equivalent.

Redis Pub/Sub (Instant Delivery)

In addition to the HTTP POST, Credpoint publishes callbacks to a Redis pub/sub channel for sub-10ms delivery. If your infrastructure has Redis access, you can subscribe to the deposit_callbacks channel to receive callbacks in real-time without waiting for the HTTP request.

// Redis pub/sub message envelope
{
  "signature": "a1b2c3...",
  "credpoint_reference": "CP-53000",
  "payload": { ...same fields as HTTP callback... }
}

The HTTP POST is always sent as a fallback. If your Redis subscriber processes the callback, you should still return 200 OK on the HTTP endpoint to prevent retries.

Retry Policy

If your callback endpoint returns a non-2xx status code or times out, Credpoint retries with exponential backoff:

Attempt Delay
1 (initial)Immediate
230 seconds
31 minute
45 minutes
515 minutes
6 (final)30 minutes

After 6 failed attempts, the callback is marked as failed and can be manually retried via the admin panel or CLI: php artisan callbacks:resend-pending.

Code Examples

PHP

<?php
$apiKey    = 'sk_live_...';
$apiSecret = 'sk_secret_...';
$payload   = json_encode([
  'external_system'    => 'skymove',
  'external_reference' => 'txn_' . uniqid(),
  'user_id'            => 'user_001',
  'user_full_name'     => 'John Doe',
  'expected_amount'    => 100000,
  'currency'           => 'NGN',
  'callback_url'       => 'https://skymoveapp.com/api/v1/credpoint/callback',
]);

$timestamp = time();
$nonce     = bin2hex(random_bytes(16));
$signature = hash_hmac('sha256', $timestamp . '.' . $nonce . '.' . $payload, $apiSecret);

$ch = curl_init('https://credpoint.net/api/v1/deposits/initiate');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'Content-Type: application/json',
  'X-API-Key: ' . $apiKey,
  'X-Timestamp: ' . $timestamp,
  'X-Nonce: ' . $nonce,
  'X-Signature: ' . $signature,
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

Node.js / JavaScript

const crypto = require('crypto');

const API_KEY    = 'sk_live_...';
const API_SECRET = 'sk_secret_...';

const payload = JSON.stringify({
  external_system: 'skymove',
  external_reference: 'txn_' + Date.now(),
  user_id: 'user_001',
  user_full_name: 'John Doe',
  expected_amount: 100000,
  currency: 'NGN',
  callback_url: 'https://skymoveapp.com/api/v1/credpoint/callback'
});

const timestamp = Math.floor(Date.now() / 1000);
const nonce     = crypto.randomBytes(16).toString('hex');
const message   = `${timestamp}.${nonce}.${payload}`;
const signature = crypto.createHmac('sha256', API_SECRET).update(message).digest('hex');

const res = await fetch('https://credpoint.net/api/v1/deposits/initiate', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key':     API_KEY,
    'X-Timestamp':   String(timestamp),
    'X-Nonce':       nonce,
    'X-Signature':   signature,
  },
  body: payload,
});
const data = await res.json();
console.log(data);

Python

import hmac, hashlib, json, time, secrets, requests

api_key    = 'sk_live_...'
api_secret = 'sk_secret_...'

payload = json.dumps({
    "external_system": "skymove",
    "external_reference": f"txn_{int(time.time())}",
    "user_id": "user_001",
    "user_full_name": "John Doe",
    "expected_amount": 100000,
    "currency": "NGN",
    "callback_url": "https://skymoveapp.com/api/v1/credpoint/callback"
})

timestamp = int(time.time())
nonce     = secrets.token_hex(16)
message   = f"{timestamp}.{nonce}.{payload}"
signature = hmac.new(api_secret.encode(), message.encode(), hashlib.sha256).hexdigest()

res = requests.post(
    'https://credpoint.net/api/v1/deposits/initiate',
    data=payload,
    headers={
        'Content-Type': 'application/json',
        'X-API-Key':     api_key,
        'X-Timestamp':   str(timestamp),
        'X-Nonce':       nonce,
        'X-Signature':   signature,
    }
)
print(res.json())

Query Deposit Status

Poll the status of a deposit intent at any time using the external_reference you provided.

GET /deposits/{external_reference}

Authentication is the same as for all API requests (X-API-Key, X-Timestamp, X-Nonce, X-Signature). For GET requests, the signature is computed over an empty payload: timestamp.nonce.

Example Request

GET /api/v1/deposits/skymove_txn_12345 HTTP/1.1
Host: credpoint.net
X-API-Key: sk_live_b67lhLmfa5gtksGVqI9IQLiDgZUNyRqlJM5tsyhn
X-Timestamp: 1757049600
X-Nonce: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
X-Signature: 3f5b9c8...

Success Response — 200 OK

{
  "success": true,
  "data": {
    "credpoint_reference": "CP-53000",
    "external_reference": "skymove_txn_12345",
    "match_code": "hot tea 42",
    "status": "auto_approved",
    "expected_amount": 100000,
    "currency": "NGN",
    "expires_at": "2026-09-04T06:47:51+01:00",
    "initiated_at": "2026-09-04T03:47:51+01:00",
    "approved_at": "2026-09-04T03:50:00+01:00",
    "match_score": 92,
    "bank_reference": "TRX123456789",
    "amount": 100000,
    "fee_paid": true,
    "amount_paid": 100200,
    "callback_status": "success"
  }
}

Response Fields

Field Type Description
credpoint_referencestringCredpoint's internal reference (CP-{id}).
external_referencestringThe reference you provided.
match_codestringHuman-readable match code (e.g., "hot tea 42"). Ask customer to include in transfer narration.
statusstringpending, matched, auto_approved, manually_approved, flagged, rejected, or expired.
expected_amountfloatOriginal expected deposit amount (major units).
currencystring3-letter ISO currency code.
expires_atstringISO 8601 expiry timestamp.
initiated_atstringISO 8601 creation timestamp.
approved_atstring|nullISO 8601 approval timestamp. Only present if approved.
match_scoreint|nullMatch confidence score. Only present if approved.
bank_referencestring|nullBank transaction reference. Only present if approved.
amountfloatAmount to credit to the user's wallet (major units). May differ from expected_amount if the fee wasn't fully paid. Only present if approved.
fee_paidboolWhether the customer paid the transaction fee. If false, amount will be less than expected_amount. Only present if approved.
amount_paidfloatActual amount the customer transferred (major units). Only present if approved.
callback_statusstringCallback delivery status (success or failed). Only present if a callback has been attempted.
callback_attemptsintNumber of callback delivery attempts. Only present if a callback has been attempted.

Rate limited at 30 requests per minute per API key.

Deposit Status & Limitations

Expiration

Deposit intents expire after 3 hours if no matching bank alert is received. An expired callback is sent when this happens. Once a deposit is matched or in manual review, it will not expire — only pending deposits are eligible for expiration.

Idempotency

Every callback includes an idempotency_key (SHA-256 hash). Always use firstOrCreate or equivalent deduplication on this key to prevent double-processing if a callback is delivered multiple times. Idempotency keys remain unchanged across retries and manual resends — the same approved deposit will always produce the same idempotency key, so you can safely deduplicate on it.

Frequently Asked Questions

How do I recover an approved payment when callback delivery failed?

Poll the GET /deposits/{external_reference} endpoint. For approved deposits, the response includes amount (the exact wallet-credit amount), fee_paid, amount_paid, approved_at, match_score, and callback_status. If callback_status is failed, Credpoint will continue retrying automatically (up to 6 attempts with exponential backoff). You can credit the user's wallet using the amount field from the status response without waiting for a callback — just ensure you use the credpoint_reference as your own deduplication key to prevent double-crediting if the callback eventually arrives.

What does each status mean?

Status Meaning
pendingDeposit intent created, waiting for a matching bank alert. Expires after 3 hours.
matchedA bank alert has been paired with this intent. The match is being evaluated for auto-approval or sent for manual review.
auto_approvedMatch score ≥85, name similarity ≥50% (or match_code found), and auto-approval is enabled. A callback has been dispatched.
manually_approvedAn admin reviewed the match and approved it. A callback has been dispatched.
rejectedAn admin reviewed the match and rejected it (with notes). A callback has been dispatched.
expired3 hours passed with no matching bank alert. A callback has been dispatched.
flaggedReserved for future use. Currently, matches requiring review show as matched on the intent while the match record is pending_review.

"matched" vs "flagged": When a match is found but doesn't meet auto-approval criteria (low name similarity, declined transaction, or score <85), the intent shows matched and the internal match record is pending_review. The status flagged is reserved but not currently used.

Why would a match require manual review?

  • Name similarity < 50% — The sender name on the bank alert doesn't match the customer's registered name, and no match_code was found in the narration.
  • Declined transaction — The bank alert indicates the transaction was declined/reversed by the bank.
  • Score < 85 — The match score is below the auto-approval threshold (amount mismatch, time window edge, etc.).
  • Auto-approval disabled — The integration has auto-approval turned off; all matches go to manual review.

When a match is rejected, an admin provides review_notes explaining the reason. The rejection callback is sent to your callback_url.

Who reviews matches and how long does it take?

Credpoint admin staff review matches in the admin panel. There is no guaranteed SLA, but most reviews are completed within 1–2 business hours during business hours. Matches are queued by score (highest first). There is no auto-approval timeout for pending reviews — a matched deposit will remain in review until an admin acts on it.

Can a match in review become approved, rejected, or expired?

  • Approved: Yes — an admin can approve the match, changing the intent to manually_approved and dispatching a callback.
  • Rejected: Yes — an admin can reject the match with notes, changing the intent to rejected and dispatching a callback.
  • Expired: No — once a deposit is matched, it will not expire. Only pending deposits are subject to the 3-hour expiration.

What does "DEPOSIT_NOT_FOUND" mean after an initiation timeout?

If you receive DEPOSIT_NOT_FOUND (404) from the status endpoint, no deposit intent exists with that external_reference for your integration. If your initiation request timed out, the intent may never have been created. You can safely retry POST /deposits/initiate with the same external_reference — if the original was created, you'll get back the original intent data (idempotent). If it wasn't, a new one will be created.

Does retrying with the same external_reference return the original intent?

Yes. The initiate endpoint is idempotent. If you call POST /deposits/initiate with an external_reference that already exists for your integration, Credpoint returns the original intent data with its current status, bank account, match code, and expiry. No duplicate is created. This is safe to do after network timeouts or any retry scenario.

Do callback idempotency keys change during manual resend?

No. Idempotency keys are deterministic — they are computed from the deposit intent ID and approval timestamp (for approved deposits) or intent ID, status, and updated_at timestamp (for rejected/expired). Every retry and manual resend produces the same idempotency key. This ensures your deduplication logic works correctly even if a callback is delivered multiple times.

How are underpayments, overpayments, and multiple transfers handled?

  • Underpayments: Payments within ±₦50 of expected_amount or total_amount receive a partial amount score — strong name+time matches can still auto-approve. Payments off by ₦50–₦100 still create a match but normally go to manual review. The credited amount is adjusted: full expected_amount if the principal was covered, otherwise paid - fee. Payments beyond ±₦100 don't match and the intent expires.
  • Overpayments: Overpayments within ±₦50 can auto-approve — the customer is credited expected_amount and the excess is retained. Overpayments of ₦50–₦100 go to manual review. Larger overpayments don't match; the customer should contact support for a refund of the excess.
  • Multiple transfers: Only the first matching bank alert is paired with a deposit intent. Subsequent alerts for the same amount will not match to an already-matched intent. They remain unmatched and may be flagged as orphaned.
  • Late payments (after expiry): Once a deposit intent expires (3 hours), it is no longer eligible for matching. The bank alert will remain unmatched. The customer should contact support.

How are reversals and chargebacks handled?

When Credpoint receives a debit alert (reversal/chargeback) that matches a previously approved deposit — same bank, same account, similar amount, within 30 days — an email notification is sent to the integration's configured reversal_notification_email address. The email includes the original deposit details (reference, amount, user) and the reversal details (amount, bank, narration, date).

This is a notify-only mechanism. Credpoint does not modify the deposit status or send a callback for reversals. The integrator is responsible for reversing the wallet credit on their platform once notified. The debit alert is marked as matched to prevent it from appearing in orphaned alert reports.

To configure reversal notifications, ask your Credpoint admin to set a reversal_notification_email in the integration settings. If no email is set, reversals are logged internally but no notification is sent.

Are settlement reports available?

Credpoint tracks fee statistics per integration (total_fees_collected, total_transactions) and provides a fee report API for date ranges via the admin panel. There is currently no automated settlement payout system or scheduled reconciliation report. Contact support to request a fee report for a specific period. Settlement timing and deductions are handled manually based on your integration agreement.

State Transition Diagram

The complete set of permitted transitions for a deposit intent:

pending ──────────────────────────────────► expired
   │                                         (3-hour timeout, no match)
   │
   ▼
matched ──────► auto_approved ──► callback_sent
   │
   ├──► manually_approved ──► callback_sent
   │
   └──► rejected ──► callback_sent

   (matched deposits NEVER expire — only pending deposits expire)

Transitions:
  pending → matched           Bank alert paired with intent
  pending → expired            3-hour timeout, no matching alert
  matched → auto_approved     Score ≥85, name ≥50% (or match_code), auto-approval ON
  matched → manually_approved  Admin approves in review panel
  matched → rejected           Admin rejects with review_notes
  auto_approved → (terminal)  No further transitions
  manually_approved → (terminal) No further transitions
  rejected → (terminal)        No further transitions
  expired → (terminal)         No further transitions

Note: "flagged" status is reserved but not currently used. Matches
requiring manual review show as "matched" on the intent while the
internal match record is "pending_review".

Flag Reasons & Review Process

When a bank alert is matched to a deposit intent but doesn't meet auto-approval criteria, it enters manual review. Here are the specific reasons a payment may be flagged for review:

Flag Reason Trigger Auto-Approvable?
low_name_similaritySender name on bank alert <50% similar to registered customer name, and no match_code found in narration.No — requires admin review
low_scoreMatch score <85 (amount mismatch beyond tolerance, time window edge, weak name match).No — requires admin review
declined_transactionBank alert indicates the transaction was declined or reversed by the bank.No — requires admin review
auto_approval_disabledIntegration has auto-approval turned off; all matches go to manual review.No — requires admin review
amount_outside_tolerancePaid amount differs from expected by ₦50–₦100 (partial score awarded, but total score may still reach 85 if other signals are strong).Maybe — if name+time+dedup push score ≥85

Rejection Reasons

When an admin rejects a match, they provide review_notes — a free-text explanation. Common rejection reasons include:

  • Wrong sender: The transfer came from a different person than the registered customer.
  • Suspicious activity: Patterns indicating fraud or money laundering.
  • Amount mismatch: The paid amount is too far from the expected amount (beyond tolerance).
  • Duplicate transfer: The same alert was already matched to another intent.
  • Declined/reversed: The bank reversed the transaction after matching.

Rejection notes are stored on the match record and are visible in the admin panel. They are not included in the callback payload — integrators receive only the status: "rejected" callback. Contact support for details on a specific rejection.

Review SLA

Priority Target Response Description
High (score ≥70)Within 1 business hourStrong matches that narrowly missed auto-approval. Reviewed first.
Normal (score 50–69)Within 4 business hoursModerate matches requiring careful review.
Low (score <50)Within 1 business dayWeak matches; may indicate fraud or wrong recipient.

Business hours: Monday–Friday, 8:00 AM – 6:00 PM WAT (UTC+1). Reviews outside business hours are processed the next business day. There is no auto-approval timeout for pending reviews — a matched deposit remains in review until an admin acts on it.

Authoritative Credit Amount

When an approved callback is missed (delivery failure), the authoritative credit amount is available via the status query endpoint. The amount field in the GET /deposits/{external_reference} response is the definitive amount to credit to the user's wallet:

// Status response for an approved deposit
{
  "status": "auto_approved",
  "amount": 100000,          // ← AUTHORITATIVE credit amount (major units)
  "expected_amount": 100000, // Original expected amount
  "fee_paid": true,          // Whether fee was covered
  "amount_paid": 100200,     // Actual amount transferred by customer
  "match_score": 92,
  "approved_at": "2026-09-04T03:50:00+01:00",
  "callback_status": "failed"
}

// Credit the user's wallet with `amount`, NOT `amount_paid` or `expected_amount`.
// Use `credpoint_reference` as your deduplication key to prevent double-crediting
// if the callback eventually arrives after you've already processed the status response.

Why amount may differ from expected_amount: If the customer underpaid (didn't cover the fee), amount = paid - fee (never below zero). If they covered at least the principal, amount = expected_amount. Overpayments within tolerance credit expected_amount (excess retained).

Sandbox & Test Mode

Credpoint does not currently provide a dedicated sandbox environment. All API requests go to the production server. To test your integration safely:

Test Fixtures

Use these controlled values during development. Deposits created with these amounts will behave predictably:

Scenario Setup Expected Outcome
Exact match (auto-approve)Initiate deposit, send bank alert with exact total_amount, matching name, within time window, with match_code in narration.Score ≥85 → auto_approved
Name mismatch (manual review)Send alert with a completely different sender name, no match_code in narration.Score <85 → matched (pending review)
Underpayment within ₦50Send alert with total_amount - 50, matching name + match_code.Score ≥85 (if name+time strong) → auto_approved, amount = expected_amount
Underpayment ₦50–₦100Send alert with total_amount - 75, matching name + match_code.Score may reach 85 → auto_approved or matched (manual review)
Overpayment within ₦50Send alert with total_amount + 30, matching name + match_code.Score ≥85 → auto_approved, amount = expected_amount (excess retained)
Amount beyond ±₦100Send alert with total_amount + 200 or total_amount - 200.No match — intent expires after 3 hours
Expired (no payment)Initiate deposit, send no bank alert. Wait 3 hours.expired callback sent
Idempotent re-initiationCall POST /deposits/initiate twice with same external_reference.Second call returns original intent data, no duplicate
Status query (not found)Call GET /deposits/nonexistent_ref.404 with DEPOSIT_NOT_FOUND

Important: Since there is no sandbox, test deposits on the production server will create real intent records. They will expire after 3 hours if unmatched. To avoid clutter, use a consistent external_system prefix like test_ for your test deposits. Contact support to have test deposits cleaned up.

Production SLA & Escalation

Uptime & Availability

  • API uptime target: 99.5% (excludes scheduled maintenance)
  • Bank alert processing: Sub-second ingestion from email receipt to match evaluation
  • Auto-approval latency: <5 seconds from bank alert receipt to callback dispatch (when auto-approval criteria are met)
  • Callback delivery: <10 seconds from approval to HTTP POST (with 6 retries over 51 minutes if endpoint is unreachable)

Escalation Contacts

Severity Description Response Time
P0 — CriticalAPI down, all deposits failing, no callbacks being sent.Within 30 minutes (24/7)
P1 — HighDeposits not matching, auto-approval broken for a specific integration.Within 2 hours (business hours)
P2 — MediumIndividual deposit stuck in review, callback delivery failures.Within 4 business hours
P3 — LowGeneral questions, feature requests, documentation clarifications.Within 1 business day

How to escalate: Contact your Credpoint account manager or email [email protected]. Include your credpoint_reference (e.g., CP-53000) and a description of the issue. For P0 issues, call the emergency support line provided in your integration agreement.

Example Key Safety

All API keys, secrets, and webhook secrets shown in this documentation are examples only. They are not real credentials and will not authenticate any request. The keys shown in code examples (e.g., sk_live_..., sk_secret_..., whsec_...) are placeholders.

  • Never commit real API secrets to source control. Use environment variables or a secrets manager.
  • Never share your API secret or webhook secret in support tickets, chat, or email. Credpoint support will never ask for your secret.
  • Rotate keys immediately if you suspect a secret has been exposed. Use the admin panel → Integrations → Rotate Keys.
  • The api_key is sent in headers and is visible in transit (over HTTPS). It is not a secret — but the api_secret and webhook_secret are. Never log or expose them.
  • Prefixes matter: sk_live_ = production API key, sk_secret_ = API secret (never sent in headers), whsec_ = webhook secret (used only for callback verification). If you see a key with a different prefix, it may be invalid or from a different system.

Need Help?

Contact support to:

  • Add your callback domain to the whitelist
  • Request API credentials for a new integration
  • Rotate API keys or webhook secrets
  • Troubleshoot integration issues
  • Request a fee report or billing statement
  • Escalate a stuck deposit or callback failure