COMPLETE INTEGRATION GUIDE
Connect your system to FICA sure
This guide explains the complete integration from account setup to receiving a finished verification. You do not need to know how FICA sure works internally. Your server sends applicant information and evidence, receives a case number, and later receives the completed result by webhook or retrieves it from the result endpoint.
1. How the integration works
- Obtain a test key and keep it in server-side secret storage.
- Call
application_types.phpto discover the current fields and evidence requirements. - Submit one case to
submit.phpusingmultipart/form-data. - Save both your
external_client_idand the returned FICA surecase_id. - Wait for the signed completion webhook, or poll
status.php. - Read the final stable contract from
v1/result.php.
The submit request is asynchronous. A successful HTTP 202 means the case was accepted and queued. It does not mean the verification has finished.
Small glossary
| Term | Plain-language meaning |
|---|---|
| API base URL | The shared beginning of every FICA sure API address, such as https://api.ficasure.com/api/. |
| Endpoint | One specific API function appended to the base URL, such as submit.php. |
| Request | Information your server sends to an endpoint. |
| Response | The HTTP status and JSON information FICA sure sends back. |
| JSON | A structured text format made of named fields, objects and lists. |
| Header | Extra request information. Your API key is sent in the X-API-Key header. |
| Multipart form-data | The request format used to send ordinary fields and actual files together. |
| Webhook | A URL on your server that FICA sure calls automatically with a completed result. |
| Environment | Either test or live. The API key—not a submitted field—selects it. |
2. Quick start
What you need
- The public API base URL, normally
https://api.ficasure.com/api/. - An active FICA sure organisation.
- Test credits and a test API key while developing.
- A server-side application capable of HTTPS and multipart file uploads.
- A public HTTPS webhook URL if completed results must be pushed automatically.
Three-request connection test
Replace YOUR_CLIENT_KEY and run these requests in order. Receiving JSON from all three confirms the URL, key, environment and result access are working.
# 1. Confirm the key and balance
curl "https://api.ficasure.com/api/account.php"
-H "X-API-Key: YOUR_CLIENT_KEY"
-H "Accept: application/json"
# 2. Load the current application requirements
curl "https://api.ficasure.com/api/application_types.php"
-H "X-API-Key: YOUR_CLIENT_KEY"
-H "Accept: application/json"
# 3. After submitting a case, retrieve its status
curl "https://api.ficasure.com/api/status.php?case_id=123"
-H "X-API-Key: YOUR_CLIENT_KEY"
-H "Accept: application/json"
localhost, 127.0.0.1, a Laragon path, or host.docker.internal on your development computer. Production integrations must call the public HTTPS API URL.3. Test the integration with Postman
The supplied collection contains registration, key management, account, requirements, submission, status and result requests. It automatically saves a returned case_id after a successful submission.
- Open Postman and choose Import.
- Import FICA-sure.postman_collection.json.
- Open the collection’s Variables tab.
- Set
base_urltohttps://api.ficasure.com/apiwithout adding an endpoint filename. - Paste the test key into
api_key. Never share an exported collection containing a real key. - Set
environmenttotest. This variable is only used by key-management requests; the actual API key chooses the environment for authenticated calls. - Run Get account and credit balance, then Get application types and requirements.
- Open Submit personal verification. In Body → form-data, replace the sample values. Enable each required file row and select a local file.
- Give every intended case a new
external_client_id. - Send the request. HTTP
202is the normal queued response and the collection saves itscase_id. - Run Get status by case ID until it is no longer queued/running, then run Get stable v1 result by case ID.
Several files in Postman
Duplicate the form-data row and keep the key exactly the same. Three address files must all use proof_of_address[]. Do not rename them to proof_of_address_1 or document2.
Common Postman mistake
Do not manually add Content-Type: multipart/form-data. Postman creates the required multipart boundary when Body is set to form-data. Add only X-API-Key and Accept: application/json.
4. Organisations, keys and environments
Cases, credits, keys and webhook settings are organisation-scoped. A customer key cannot retrieve another organisation’s case. An unauthorised lookup returns 404 Not found instead of confirming whether that case exists.
Test key
Uses the test balance. Test cases remain separate from live cases and use the local PEP sandbox where applicable. They do not consume live PEP supplier quota.
Live key
Uses the live balance and production screening configuration. A live case cannot silently become a simulated local PEP result.
Do not send an environment field with a verification. FICA sure reads the environment from the key. Changing a form label to “live” while still sending a test key does not create a live case.
Recommended key setup
- Sign in to FICA sure.
- Open API Integrations.
- Under Integration Access, enter the account password.
- Create the test or live key.
- Copy it immediately into server-side secret storage. It cannot be displayed again.
Alternative: register and manage keys by API
POST /api/register.php
Content-Type: application/json
{
"organization_name": "Client X",
"full_name": "Jane Doe",
"email": "jane@example.com",
"mobile": "+27110000000",
"password": "at-least-10-characters"
}
Registration returns HTTP 201 with status pending. An administrator must activate the organisation and allocate credits before a key can be issued.
POST /api/credentials.php
Content-Type: application/json
{
"email": "jane@example.com",
"password": "CLIENT_PASSWORD",
"environment": "test",
"action": "issue"
}
| Action | Use | Effect |
|---|---|---|
issue | Create the first key for an environment. | Returns the raw key once. Returns 409 if one exists. |
rotate | Replace an existing key. | The previous key stops working immediately. |
revoke | Remove access for that environment. | No replacement key is returned. |
FICA_SURE_API_BASE_URL=https://api.ficasure.com/api/
FICA_SURE_API_KEY=the_key_returned_once_by_fica_sure
FICA_SURE_WEBHOOK_SECRET=the_webhook_secret_returned_once
5. Endpoint reference
| Method and endpoint | Purpose | Authentication | Success |
|---|---|---|---|
POST register.php | Register a pending organisation. | No API key | 201 |
POST credentials.php | Issue, rotate or revoke a key. | Email/password in body | 201/200 |
GET account.php | Confirm organisation, environment and credits. | Organisation key | 200 |
GET application_types.php | Get current fields and evidence requirements. | Organisation key | 200 |
POST submit.php | Create and queue a verification. | Organisation key | 202 |
GET status.php | Get lightweight processing progress. | Same environment key | 200 |
GET v1/result.php | Get the stable completed-result contract. | Same environment key | 200 |
X-API-Key: YOUR_CLIENT_KEY
Accept: application/json
6. Application types, fields and evidence
Always load requirements first
GET /api/application_types.php
X-API-Key: YOUR_CLIENT_KEY
This response is authoritative. Administrators can change whether a supported field or document is required, so do not permanently hard-code one personal form.
| Mode | What your form should do |
|---|---|
required | Collect it. The case cannot be FICA-complete without a usable/resolved item. |
optional | Allow it without blocking submission when absent. |
edd_only | Collect it when enhanced due diligence applies. |
ignore | Do not show or send it for that application type. |
Current application keys include personal, sole_proprietor, partnership, close_corporation, private_company, personal_liability_company, public_company, trust, estate, association_society_club_trade_union, npc_npo, and merchant_card_acceptance. Use the returned key, not its visible label.
Request metadata
| Field | Required? | Meaning |
|---|---|---|
external_client_id | Yes | Your unique, non-secret reference for this case. Store it. |
application_type | Recommended | A key from application_types.php. Validate it before sending. |
source_app | No | A short identifier for your server, such as client-portal. |
application_reference | No | Your business reference. FICA sure generates one if omitted. |
personal_profile_client_id | Linked business flows | Links a business application to a previously checked personal profile where configured. |
name_matching_level | No | Controls name-list comparison for this case. |
Evidence rules
- Use
multipart/form-data. - File keys must exactly match a non-ignored
document_modeskey. - Supported content types are JPEG, PNG, WebP and PDF. The server checks the real MIME type.
- The current limit is 15 MB per file. Hosting/proxy limits may be lower.
- Send up to three files per configured evidence type and up to five
supporting_document[]files. - Every repeated file is preserved and assessed separately.
Correct:
proof_of_address[] = address-letter.pdf
proof_of_address[] = municipal-account.jpg
proof_of_address[] = property-photo.webp
Incorrect:
proof_of_address_1
proofOfAddress
address_document
document2
7. Exact, Fuzzy 1 and Fuzzy 2 name checks
The website uses plain-language labels. API integrations select the same behaviour by sending name_matching_level with each case. The choice affects local FIC/UN name comparison and the configured PEP name search. It does not replace evidence, identity or date-of-birth checks.
| Value | Website description | Technical name | Behaviour |
|---|---|---|---|
0 | Closest spelling only | Exact | Compares the normalised spelling. Letter case, spacing, punctuation and accents are normalised. |
1 | Allow small differences | Fuzzy 1 | Includes small spelling differences and common name variations. This is the recommended default. |
2 | Search more broadly | Fuzzy 2 | Includes broader spelling and name-order variations and may return more possible matches for review. |
# Exact / closest spelling only
-F "name_matching_level=0"
# Fuzzy 1 / allow small differences
-F "name_matching_level=1"
# Fuzzy 2 / search more broadly
-F "name_matching_level=2"
If omitted or invalid, the configured standard comparison is used. The final result confirms it in case.name_matching.level and case.name_matching.description.
8. Submit a verification
curl -X POST "${FICA_SURE_API_BASE_URL}submit.php"
-H "X-API-Key: ${FICA_SURE_API_KEY}"
-H "Accept: application/json"
-F "external_client_id=client-x-000123"
-F "source_app=client-x-website"
-F "application_type=personal"
-F "application_reference=ONBOARDING-000123"
-F "name_matching_level=1"
-F "full_name=Jane Doe"
-F "id_number=0000000000000"
-F "date_of_birth=1990-01-31"
-F "nationality=ZA"
-F "email=jane@example.com"
-F "mobile_number=+27110000000"
-F "terms_accepted=1"
-F "identity_document[]=@/secure/path/id.jpg"
-F "proof_of_address[]=@/secure/path/address.pdf"
-F "proof_of_address[]=@/secure/path/municipal-account.jpg"
-F "selfie[]=@/secure/path/selfie.jpg"
Replace every sample value and only send fields permitted by the selected application type.
Successful queued response
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"success": true,
"case_id": 123,
"status": "queued",
"environment": "test",
"credits_reserved": 8
}
Save case_id, external_client_id, and environment immediately. If the connection breaks after sending, query by the same external ID before retrying so an accidental duplicate is not created.
Minimal PHP example
$post = [
'external_client_id' => 'client-x-000123',
'application_type' => 'personal',
'name_matching_level' => '1',
'full_name' => 'Jane Doe',
'identity_document[0]' => new CURLFile('/secure/id.jpg', 'image/jpeg', 'id.jpg'),
];
$curl = curl_init(getenv('FICA_SURE_API_BASE_URL') . 'submit.php');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 90,
CURLOPT_HTTPHEADER => ['X-API-Key: '.getenv('FICA_SURE_API_KEY'), 'Accept: application/json'],
CURLOPT_POSTFIELDS => $post,
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($body === false) throw new RuntimeException(curl_error($curl));
$response = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
if ($status !== 202) throw new RuntimeException($response['error'] ?? 'Submission failed');
9. Progress and stable results
GET /api/status.php?case_id=123
X-API-Key: YOUR_CLIENT_KEY
# Your identifier also works:
GET /api/status.php?external_client_id=client-x-000123
| Processing value | Meaning | Action |
|---|---|---|
queued | Waiting for a worker. | Wait and check again. |
running | Automated checks are active. | Wait and check again. |
completed | Automated processing finished. | Retrieve v1 result and inspect the decision. |
waiting_human_response | An administrator must decide. | Do not approve. Wait for review. |
failed | A technical stage stopped. | Do not approve; show a safe pending/error message. |
Stable v1 contract
GET /api/v1/result.php?case_id=123
X-API-Key: YOUR_CLIENT_KEY
# Or:
GET /api/v1/result.php?external_client_id=client-x-000123
X-API-Key: YOUR_CLIENT_KEY
Use this versioned endpoint in application code. It returns schema.name = cris_fica_result and schema.version = 1.0, with stable profile/document slots for every application type.
{
"schema": {"name": "cris_fica_result", "version": "1.0"},
"success": true,
"case": {
"case_id": 123,
"external_client_id": "client-x-000123",
"application_type": "personal",
"name_matching": {"level": "1", "description": "Small spelling differences and common name variations"}
},
"processing": {"status": "completed", "stages": []},
"decision": {"status": "approved", "identity_verified": true, "fica_complete": true, "application_access": "allow", "human_review_required": false},
"completion": {},
"profile": {"fields": []},
"requirements": {"outstanding": [], "mismatches": [], "review_flags": []},
"documents": [],
"screening": {},
"human_review": {},
"customer_message": "",
"usage": {},
"billing": {}
}
Making an access decision
- HTTP 200 only means the result was retrieved.
- Inspect
decision.status,decision.fica_complete,decision.application_access, anddecision.human_review_requiredtogether. - If human review is required or screening is not clear, never auto-approve.
- Show
customer_messageto the customer. Do not expose raw provider data or administrator diagnostics.
10. Receive completed results by webhook
A webhook means FICA sure calls your server when the result changes, so you do not need to poll continuously.
- Create a public endpoint, for example
https://client.example.com/webhooks/fica-sure. - Open API Integrations → Integration Access → Completion webhook.
- Enter the HTTPS URL and enable delivery.
- Confirm the account password and save.
- Copy the signing secret immediately; it cannot be shown again.
Production endpoints must use HTTPS, resolve publicly and cannot point to private/reserved addresses or include embedded credentials. A hosted server cannot call localhost on your computer.
| Header | Meaning |
|---|---|
X-FICA-Signature | sha256= plus the HMAC-SHA256 of the exact raw body. |
X-FICA-Event | case.completed, case.failed, or case.reviewed. |
X-FICA-Delivery-ID | A unique delivery ID. Store it to handle duplicates safely. |
X-FICA-Case-ID | The numeric case ID. |
PHP receiver
$raw = file_get_contents('php://input');
$received = $_SERVER['HTTP_X_FICA_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $raw, getenv('FICA_SURE_WEBHOOK_SECRET'));
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit;
}
$deliveryId = $_SERVER['HTTP_X_FICA_DELIVERY_ID'] ?? '';
// If this ID was processed before, return 204 without processing twice.
$result = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
// Save or queue the result, then acknowledge quickly.
http_response_code(204);
Node.js/Express receiver
import crypto from 'node:crypto';
app.post('/webhooks/fica-sure', express.raw({type: 'application/json'}), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', process.env.FICA_SURE_WEBHOOK_SECRET).update(req.body).digest('hex');
const received = req.get('X-FICA-Signature') || '';
const valid = expected.length === received.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
if (!valid) return res.sendStatus(401);
const result = JSON.parse(req.body.toString('utf8'));
// Store idempotently using X-FICA-Delivery-ID.
return res.sendStatus(204);
});
Return 2xx quickly. FICA sure records the attempt and HTTP status. Keep status/result polling as recovery if your endpoint was unavailable during delivery.
11. Credits
GET /api/account.php
X-API-Key: YOUR_CLIENT_KEY
The key selects the test or live balance. Read credits.balance, credits.reserved, and credits.available. A case reserves an estimate when accepted; successful processing finalises it; technical failure releases/refunds it. Insufficient available credit rejects the submission.
12. Errors and troubleshooting
| Status | Meaning | Check |
|---|---|---|
| 400 | Malformed request. | Method and body encoding. |
| 401 | Credential rejected. | Correct key/password, active account and no extra spaces. |
| 403 | Wrong credential type or permission. | Use an organisation key, not the WordPress connector key. |
| 404 | Endpoint/case not found or isolated. | URL, case ID, organisation and same test/live key. |
| 405 | Wrong HTTP method. | Use POST where documented. |
| 409 | Key already exists. | Rotate only when intentionally replacing it. |
| 422 | Request cannot be accepted. | Fields, application/document keys, file count/type/size, credits and account state. |
| 429 | Too many requests. | Back off; avoid tight polling. |
| 500–599 | Server/upstream issue. | Record safe diagnostics and query status before resubmitting. |
“FICA API unavailable”
- Test from the client server, not only your browser.
- Confirm the base URL ends in
/api/. - Confirm the response is JSON, not HTML, a hosting login or firewall challenge.
- Check DNS, TLS, outbound firewall, ModSecurity and PHP cURL.
- Test
account.phpwith the organisation key.
Test case not found with a live key
This is intentional. Use the same environment key that created the case.
Only one uploaded file appears
Use repeated canonical keys with brackets and confirm your library did not overwrite earlier map entries.
Live PEP screening error
Confirm the live organisation key created the case and live screening is enabled with a valid administrator-managed credential. A failed live call is not silently replaced with a local clear result.
Safe diagnostics
Record request time, endpoint, method, HTTP status, external_client_id, returned case_id, environment and a redacted response. Never record the key, password, webhook secret, evidence contents or unredacted personal data.
13. Production checklist
- Use the public HTTPS API URL.
- Keep secrets on the server only.
- Store separate test and live keys.
- Check
account.phpand returned environment. - Load
application_types.php. - Honour required, optional, EDD-only and ignored modes.
- Use a unique external ID per intended case.
- Choose the name matching level.
- Send files as repeated canonical multipart keys.
- Save both identifiers.
- Treat 202 as queued, not approved.
- Use
v1/result.php. - Check decision, completion, access and human review together.
- Verify webhook signatures over the raw body.
- Deduplicate delivery IDs.
- Return 2xx quickly and keep polling as fallback.
- Handle ambiguous submissions without creating duplicates.
- Never expose provider identities, supplier responses, secrets or administrator diagnostics.