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-Key | Your integration API key. |
| X-Timestamp | Current Unix timestamp (within ±5 minutes of server time). |
| X-Nonce | Unique random string per request (16+ bytes hex recommended). |
| X-Signature | HMAC-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 webhook | 100 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.
/deposits/initiate
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| external_system | string | yes | Your platform/system identifier (a-z, 0-9, _, -). |
| external_reference | string | yes | Unique transaction reference in your system. Must be unique per deposit. |
| user_id | string | yes | Customer ID in your platform. |
| user_full_name | string | yes | Customer full name (letters, spaces, ., -, '). |
| expected_amount | decimal | yes | Amount to deposit (excluding fee). Max: ₦10,000,000 or integration limit. |
| currency | string | no | 3-letter ISO currency (default: NGN, uppercase). |
| user_identifier | string | no | Additional customer identifier (email, phone, etc.). |
| callback_url | url | yes | HTTPS 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_urldomain must be whitelisted in Credpoint's config. Subdomains of whitelisted domains are automatically allowed (e.g.,staging.api.yourplatform.comis allowed ifyourplatform.comis 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_HEADERS | 401 | One or more auth headers are missing. |
| INVALID_API_KEY | 401 | Unknown or inactive API key. |
| INVALID_NONCE | 401 | Nonce was already used. |
| INVALID_SIGNATURE | 401 | HMAC signature does not match. |
| INVALID_TIMESTAMP | 401 | Timestamp is more than 5 minutes off. |
| INTEGRATION_INACTIVE | 403 | Integration is suspended or inactive. |
| VALIDATION_ERROR | 400 | Request body failed validation. |
| AMOUNT_EXCEEDS_MAX | 400 | Deposit exceeds integration max deposit amount. |
| INVALID_CALLBACK_URL | 400 | Callback URL must use HTTPS in production. |
| INVALID_CALLBACK_DOMAIN | 400 | Callback domain is not whitelisted. |
| NO_BANK_ACCOUNT | 503 | No 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-Signature | HMAC-SHA256 of the JSON payload body, signed with your webhook_secret (not the API secret). |
| X-Credpoint-Reference | Credpoint's internal reference (e.g., CP-53000). |
| Content-Type | Always 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_reference | string | The reference you provided when initiating the deposit. All statuses. |
| status | string | "approved", "rejected", or "expired". All statuses. |
| currency | string | 3-letter ISO currency code. All statuses. |
| credpoint_reference | string | Credpoint's internal reference (CP-{id}). All statuses. |
| idempotency_key | string | SHA-256 hash for duplicate prevention. All statuses. |
| expected_amount | float | Original expected deposit amount (major units). Rejected/expired only. |
| expires_at | string | ISO 8601 expiry timestamp. Rejected/expired only. |
| amount | float | Amount to credit to the user's wallet (major units). May differ from expected_amount if the fee wasn't fully paid. Approved only. |
| approved_at | string | ISO 8601 timestamp of approval. Approved only. |
| match_score | int|null | Match confidence score (0–100). May be null. Approved only. |
| bank_reference | string|null | Bank transaction reference from the alert. May be null. Approved only. |
| bank_alert_id | int | Internal Credpoint bank alert ID. Approved only. |
| fee_paid | bool | Whether the customer paid the transaction fee. If false, amount will be less than expected_amount. Defaults to true. Approved only. |
| amount_paid | float | Actual 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 |
| 2 | 30 seconds |
| 3 | 1 minute |
| 4 | 5 minutes |
| 5 | 15 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.
/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_reference | string | Credpoint's internal reference (CP-{id}). |
| external_reference | string | The reference you provided. |
| match_code | string | Human-readable match code (e.g., "hot tea 42"). Ask customer to include in transfer narration. |
| status | string | pending, matched, auto_approved, manually_approved, flagged, rejected, or expired. |
| expected_amount | float | Original expected deposit amount (major units). |
| currency | string | 3-letter ISO currency code. |
| expires_at | string | ISO 8601 expiry timestamp. |
| initiated_at | string | ISO 8601 creation timestamp. |
| approved_at | string|null | ISO 8601 approval timestamp. Only present if approved. |
| match_score | int|null | Match confidence score. Only present if approved. |
| bank_reference | string|null | Bank transaction reference. Only present if approved. |
| amount | float | Amount 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_paid | bool | Whether the customer paid the transaction fee. If false, amount will be less than expected_amount. Only present if approved. |
| amount_paid | float | Actual amount the customer transferred (major units). Only present if approved. |
| callback_status | string | Callback delivery status (success or failed). Only present if a callback has been attempted. |
| callback_attempts | int | Number 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 |
|---|---|
| pending | Deposit intent created, waiting for a matching bank alert. Expires after 3 hours. |
| matched | A bank alert has been paired with this intent. The match is being evaluated for auto-approval or sent for manual review. |
| auto_approved | Match score ≥85, name similarity ≥50% (or match_code found), and auto-approval is enabled. A callback has been dispatched. |
| manually_approved | An admin reviewed the match and approved it. A callback has been dispatched. |
| rejected | An admin reviewed the match and rejected it (with notes). A callback has been dispatched. |
| expired | 3 hours passed with no matching bank alert. A callback has been dispatched. |
| flagged | Reserved 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_approvedand dispatching a callback. - Rejected: Yes — an admin can reject the match with notes, changing the intent to
rejectedand dispatching a callback. - Expired: No — once a deposit is
matched, it will not expire. Onlypendingdeposits 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_amountortotal_amountreceive 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 creditedamountis adjusted: fullexpected_amountif the principal was covered, otherwisepaid - fee. Payments beyond ±₦100 don't match and the intent expires. - Overpayments: Overpayments within ±₦50 can auto-approve — the customer is credited
expected_amountand 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_similarity | Sender name on bank alert <50% similar to registered customer name, and no match_code found in narration. | No — requires admin review |
| low_score | Match score <85 (amount mismatch beyond tolerance, time window edge, weak name match). | No — requires admin review |
| declined_transaction | Bank alert indicates the transaction was declined or reversed by the bank. | No — requires admin review |
| auto_approval_disabled | Integration has auto-approval turned off; all matches go to manual review. | No — requires admin review |
| amount_outside_tolerance | Paid 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 hour | Strong matches that narrowly missed auto-approval. Reviewed first. |
| Normal (score 50–69) | Within 4 business hours | Moderate matches requiring careful review. |
| Low (score <50) | Within 1 business day | Weak 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 ₦50 | Send alert with total_amount - 50, matching name + match_code. | Score ≥85 (if name+time strong) → auto_approved, amount = expected_amount |
| Underpayment ₦50–₦100 | Send alert with total_amount - 75, matching name + match_code. | Score may reach 85 → auto_approved or matched (manual review) |
| Overpayment within ₦50 | Send alert with total_amount + 30, matching name + match_code. | Score ≥85 → auto_approved, amount = expected_amount (excess retained) |
| Amount beyond ±₦100 | Send 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-initiation | Call 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 — Critical | API down, all deposits failing, no callbacks being sent. | Within 30 minutes (24/7) |
| P1 — High | Deposits not matching, auto-approval broken for a specific integration. | Within 2 hours (business hours) |
| P2 — Medium | Individual deposit stuck in review, callback delivery failures. | Within 4 business hours |
| P3 — Low | General 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_keyis sent in headers and is visible in transit (over HTTPS). It is not a secret — but theapi_secretandwebhook_secretare. 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