Implementation Guide
1. Webhook Endpoint Setup for Business Verification
// Express.js example
const express = require('express');
const crypto = require('crypto');
const app = express();
// IMPORTANT: Use raw body for signature verification
app.use(express.raw({ type: 'application/json' }));
app.post('/webhooks/aiprise/business/events', async (req, res) => {
try {
// Step 1: Verify signature
const signature = req.headers['x-hmac-signature'];
const rawBody = req.body.toString('utf8');
const expectedSignature = crypto
.createHmac('sha256', process.env.AIPRISE_API_KEY)
.update(rawBody)
.digest('hex');
if (signature !== expectedSignature) {
console.error('Invalid signature');
return res.status(401).send('Unauthorized');
}
// Step 2: Parse the payload
const payload = JSON.parse(rawBody);
// Step 3: Identify event type
const eventType = payload.event_type || payload.business_profile_event_type;
// Step 4: Process based on event type
await processBusinessEvent(payload, eventType);
// Step 5: Respond quickly (200 OK)
res.status(200).send('OK');
} catch (error) {
console.error('Business webhook processing error:', error);
res.status(500).send('Internal Server Error');
}
});
async function processBusinessEvent(payload, eventType) {
switch(eventType) {
case 'VERIFICATION_SESSION_STARTED':
await handleBusinessSessionStarted(payload);
break;
case 'VERIFICATION_SESSION_COMPLETION':
await handleBusinessSessionCompletion(payload);
break;
case 'RUN_BUSINESS_VERIFICATION':
await handleBusinessProfileVerification(payload);
break;
case 'BUSINESS_OFFICER_RUN_KYC':
await handleOfficerKYC(payload);
break;
case 'ADD_BUSINESS_OFFICER':
await handleOfficerAdded(payload);
break;
case 'AML_MONITORING_UPDATE_BUSINESS_VERIFICATION':
case 'AML_MONITORING_UPDATE_RELATED_BUSINESS_OFFICER':
await handleBusinessAMLUpdate(payload);
break;
default:
console.log(`Unhandled business event type: ${eventType}`);
}
}
async function handleBusinessSessionStarted(payload) {
await db.businessVerificationSessions.update(
{ id: payload.verification_session_id },
{
status: 'IN_PROGRESS',
started_at: new Date()
}
);
// Notify compliance team
await notifyComplianceTeam(
payload.client_reference_id,
'New business verification started'
);
}
async function handleBusinessProfileVerification(payload) {
const decision = payload.aiprise_summary.decision;
const businessProfileId = payload.business_profile_id;
// Update business profile
await db.businessProfiles.update(
{ id: businessProfileId },
{
verification_status: decision,
verified_at: decision === 'APPROVED' ? new Date() : null,
kyb_completed: true
}
);
// Process officers
if (payload.related_persons) {
for (const officer of payload.related_persons) {
await db.businessOfficers.upsert({
business_profile_id: businessProfileId,
person_reference_id: officer.person_reference_id,
roles: officer.roles,
ownership_percent: officer.ownership_percent,
verification_status: officer.verification_status
});
}
}
// Process related companies
if (payload.related_companies) {
for (const company of payload.related_companies) {
await db.relatedCompanies.upsert({
business_profile_id: businessProfileId,
company_reference_id: company.company_reference_id,
legal_name: company.legal_name,
relationship_type: company.relationship_type,
ownership_percent: company.ownership_percent
});
}
}
// Business logic based on decision
if (decision === 'APPROVED') {
await enableBusinessAccount(businessProfileId);
await setTransactionLimits(businessProfileId, payload.risk_info);
await generateServiceAgreement(businessProfileId);
} else if (decision === 'DECLINED') {
await notifyBusinessDecline(businessProfileId, payload.aiprise_summary.reasons);
}
}
async function handleOfficerKYC(payload) {
const businessProfileId = payload.business_profile_id;
const personReferenceId = payload.person_reference_id;
const decision = payload.aiprise_summary.decision;
// Update officer record
await db.businessOfficers.update(
{
business_profile_id: businessProfileId,
person_reference_id: personReferenceId
},
{
kyc_status: decision,
kyc_completed_at: new Date(),
aml_status: payload.aml_info?.result
}
);
// Check if all officers are verified
const allOfficers = await db.businessOfficers.findAll({
where: { business_profile_id: businessProfileId }
});
const allVerified = allOfficers.every(
officer => officer.kyc_status === 'APPROVED'
);
if (allVerified) {
await db.businessProfiles.update(
{ id: businessProfileId },
{ all_officers_verified: true }
);
// Trigger next step in onboarding
await triggerBusinessOnboardingStep(businessProfileId, 'OFFICERS_VERIFIED');
}
}
app.listen(3000);2. Decision Callback Handler for Business
// Separate endpoint for business decision callbacks
app.post('/webhooks/aiprise/business/decision', async (req, res) => {
try {
// Verify signature (same as above)
const signature = req.headers['x-hmac-signature'];
const rawBody = req.body.toString('utf8');
const expectedSignature = crypto
.createHmac('sha256', process.env.AIPRISE_API_KEY)
.update(rawBody)
.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).send('Unauthorized');
}
const payload = JSON.parse(rawBody);
// Process final KYB decision
await processBusinessDecision(payload);
res.status(200).send('OK');
} catch (error) {
console.error('Business decision callback error:', error);
res.status(500).send('Internal Server Error');
}
});
async function processBusinessDecision(payload) {
const decision = payload.aiprise_summary.decision;
const businessId = payload.client_reference_id;
// Make idempotent using verification_session_id
const existing = await db.businessVerificationResults.findOne({
verification_session_id: payload.verification_session_id
});
if (existing) {
console.log('Business decision already processed');
return;
}
// Store KYB result
await db.businessVerificationResults.create({
verification_session_id: payload.verification_session_id,
business_id: businessId,
decision: decision,
payload: payload,
processed_at: new Date()
});
// Update business record
await db.businesses.update(
{ id: businessId },
{
kyb_status: decision,
kyb_completed_at: new Date(),
registry_verified: payload.registration_info?.good_standing,
aml_clear: payload.aml_info?.result === 'CLEAR',
risk_tier: payload.risk_info?.risk_tier,
credit_score: payload.lookup_info?.find(l => l.provider === 'credit_bureau')?.credit_score
}
);
// Store officers
if (payload.related_persons) {
for (const officer of payload.related_persons) {
await db.businessOfficers.create({
business_id: businessId,
person_reference_id: officer.person_reference_id,
first_name: officer.user_profile.first_name,
last_name: officer.user_profile.last_name,
roles: officer.roles,
ownership_percent: officer.ownership_percent,
kyc_status: officer.verification_status
});
}
}
// Business decision logic
switch(decision) {
case 'APPROVED':
await approveBusinessOnboarding(businessId, payload);
break;
case 'DECLINED':
await declineBusinessOnboarding(businessId, payload.aiprise_summary.reasons);
break;
case 'MANUAL_REVIEW':
await queueForComplianceReview(businessId, payload);
break;
}
}
async function approveBusinessOnboarding(businessId, payload) {
// Enable business account
await db.businesses.update(
{ id: businessId },
{
account_status: 'ACTIVE',
activated_at: new Date()
}
);
// Set transaction limits based on risk
const riskTier = payload.risk_info?.risk_tier;
const limits = calculateTransactionLimits(riskTier);
await db.businessLimits.create({
business_id: businessId,
daily_limit: limits.daily,
monthly_limit: limits.monthly,
transaction_limit: limits.transaction
});
// Generate contract
await generateBusinessContract(businessId);
// Notify business
await sendBusinessApprovalEmail(businessId);
// Notify account manager
const accountManager = payload.client_reference_data?.account_manager;
if (accountManager) {
await notifyAccountManager(accountManager, businessId, 'approved');
}
}3. Officer Management Handler
async function handleOfficerAdded(payload) {
const businessProfileId = payload.business_profile_id;
const officer = payload.user_profile;
const personReferenceId = payload.person_reference_id;
// Create officer record
await db.businessOfficers.create({
business_profile_id: businessProfileId,
person_reference_id: personReferenceId,
first_name: officer.first_name,
last_name: officer.last_name,
email: officer.email,
phone: officer.phone,
roles: payload.roles,
ownership_percent: payload.ownership_percent,
shares_allocated: payload.shares_allocated,
kyc_status: 'PENDING',
created_at: new Date()
});
// Check if KYC is required for this officer
const requiresKYC = payload.roles.some(role =>
['CEO', 'CFO', 'DIRECTOR', 'UBO'].includes(role)
) || payload.ownership_percent >= 25;
if (requiresKYC) {
// Generate KYC link for officer
const kycUrl = await aipriseAPI.createOfficerKYCSession({
business_profile_id: businessProfileId,
person_reference_id: personReferenceId
});
// Send KYC invitation
await sendOfficerKYCInvitation(officer.email, kycUrl);
}
}
async function handleOfficerKYCCompletion(payload) {
const businessProfileId = payload.business_profile_id;
const personReferenceId = payload.person_reference_id;
const kycDecision = payload.aiprise_summary.decision;
// Update officer KYC status
await db.businessOfficers.update(
{
business_profile_id: businessProfileId,
person_reference_id: personReferenceId
},
{
kyc_status: kycDecision,
kyc_completed_at: new Date(),
id_verified: payload.id_info?.authenticity?.result === 'PASS',
face_matched: payload.face_match_info?.result === 'PASS',
aml_status: payload.aml_info?.result
}
);
// If officer is declined, flag for review
if (kycDecision === 'DECLINED') {
await createComplianceTicket({
business_profile_id: businessProfileId,
issue_type: 'OFFICER_KYC_DECLINED',
person_reference_id: personReferenceId,
details: payload.aiprise_summary.reasons
});
}
// Check overall business verification progress
await checkBusinessVerificationProgress(businessProfileId);
}Examples
Example 1: Simple KYB Session Flow (Standalone)
Scenario: Business completes KYB, not linked to profile
1. Business initiates KYB
→ Your app creates verification session
2. Event: VERIFICATION_SESSION_STARTED
{
"event_type": "VERIFICATION_SESSION_STARTED",
"verification_session_id": "bvs_123",
"client_reference_id": "business_456"
}
→ Log session start
→ Notify compliance team
3. Business submits company documents
4. Event: VERIFICATION_REQUEST_SUBMITTED
{
"event_type": "VERIFICATION_REQUEST_SUBMITTED",
"verification_session_id": "bvs_123"
}
→ Update status: "Processing your business verification"
5. KYB verification completes
6. Event: VERIFICATION_SESSION_COMPLETION
{
"event_type": "VERIFICATION_SESSION_COMPLETION",
"aiprise_summary": { "decision": "APPROVED" },
"business_info": { /* ... */ },
"registration_info": { /* ... */ },
"related_persons": [ /* officers */ ]
}
7. Decision Callback: callback_url
{
"verification_session_id": "bvs_123",
"aiprise_summary": { "decision": "APPROVED" },
// Full KYB payload
}
→ Enable business account
→ Generate service agreement
→ Notify business: "Verification successful"
Example 2: Business Profile with Officers KYC
Scenario: Complete business onboarding with officer verification
1. Create business profile
2. Event: BUSINESS_PROFILE_CREATE
{
"business_profile_id": "bp_789",
"business_profile_event_type": "BUSINESS_PROFILE_CREATE"
}
→ Profile created
3. Run business verification
4. Event: RUN_BUSINESS_VERIFICATION
{
"business_profile_id": "bp_789",
"business_profile_event_type": "RUN_BUSINESS_VERIFICATION",
"aiprise_summary": { "decision": "APPROVED" },
// Embedded KYB payload
}
→ Business entity verified
→ Now need to verify officers
5. Add CEO officer
6. Event: ADD_BUSINESS_OFFICER
{
"business_profile_id": "bp_789",
"person_reference_id": "person_ceo_001",
"roles": ["CEO", "DIRECTOR"],
"ownership_percent": 45.0
}
→ Officer added
→ Generate KYC link
7. Event: BUSINESS_OFFICER_CREATE_SESSION_URL
{
"business_profile_id": "bp_789",
"person_reference_id": "person_ceo_001",
"session_url": "https://verify.aiprise.com/officer/xyz"
}
→ Send KYC link to CEO email
8. CEO completes KYC
9. Event: BUSINESS_OFFICER_RUN_KYC
{
"business_profile_id": "bp_789",
"person_reference_id": "person_ceo_001",
"aiprise_summary": { "decision": "APPROVED" },
// Embedded User KYC payload
}
→ CEO verified
→ Check if all required officers verified
→ If yes: Complete business onboarding
Example 3: AML Monitoring Alert on Business and Officer
Scenario: Sanctions match found during ongoing monitoring
1. Daily AML monitoring runs on business profile
2. Event: AML_MONITORING_UPDATE_BUSINESS_VERIFICATION
{
"business_profile_id": "bp_789",
"aml_monitoring_update": {
"status": "MATCH_FOUND",
"matches": [{
"list_name": "OFAC SDN",
"entity_name": "Acme Corporation Ltd",
"confidence": 0.96
}]
}
}
→ URGENT: Freeze business account immediately
→ Notify compliance team
→ Create high-priority investigation ticket
→ Suspend all transactions
3. Simultaneously, officer match found
4. Event: AML_MONITORING_UPDATE_RELATED_BUSINESS_OFFICER
{
"business_profile_id": "bp_789",
"person_reference_id": "person_ceo_001",
"aml_monitoring_update": {
"status": "MATCH_FOUND",
"matches": [{
"list_name": "PEP Database",
"entity_name": "John Smith",
"confidence": 0.91
}]
}
}
→ Additional concern: Officer is PEP
→ Escalate to senior compliance
→ Request enhanced due diligence
→ Document all findings for regulators
Example 4: Related Company Verification
Scenario: Verifying parent company as part of UBO identification
1. Business profile created and verified
2. Add parent company
3. Event: ADD_RELATED_COMPANY
{
"business_profile_id": "bp_789",
"company_reference_id": "company_parent_001",
"legal_name": "Acme Holdings PLC",
"relationship_type": "PARENT",
"ownership_percent": 100.0
}
→ Parent company added
→ Trigger KYB for parent
4. Parent company KYB completes
5. Event: RELATED_COMPANY_RUN_KYB
{
"business_profile_id": "bp_789",
"company_reference_id": "company_parent_001",
"aiprise_summary": { "decision": "APPROVED" },
// Embedded parent company KYB payload
"related_persons": [ /* parent company officers */ ]
}
→ Parent company verified
→ Ultimate beneficial owners identified
→ Complete ownership chain
→ Update risk assessment with group structure
Example 5: Manual Review and Override
Scenario: Complex business structure requires manual review and override
1. Initial business verification completes
2. Event: VERIFICATION_SESSION_COMPLETION
{
"aiprise_summary": { "decision": "MANUAL_REVIEW" },
"verification_session_id": "bvs_123"
}
→ Complex ownership structure detected
→ Queued for manual review
3. Compliance analyst reviews case
4. Analyst requests additional documents
5. Event: COMMENT
{
"business_profile_id": "bp_789",
"comment": "Requesting: 1) Updated ownership chart, 2) Board resolution, 3) Proof of business address"
}
→ Notification sent to business
→ Documents requested via portal
6. Business uploads documents
7. Event: ADD_ADDITIONAL_MEDIA
{
"business_profile_id": "bp_789",
"file_uuid": "file_doc_123",
"media_type": "OWNERSHIP_CHART"
}
→ Document received
→ Notify analyst for review
8. Analyst approves after document review
9. Event: RELATED_CASE_STATUS_UPDATE
{
"business_profile_id": "bp_789",
"verification_result": "APPROVED",
"author_id": "[email protected]",
"reason": "Additional documentation verified. Complex but legitimate ownership structure confirmed."
}
→ Override applied
→ Business account activated
→ Generate approval notification
→ Log override for auditUpdated about 6 hours ago
