Integration Scenarios
End-to-end test scripts for validating your Zenoo integration. Run these against the staging environment before going live.
Replace {project_hash} with your staging project hash and staging-key with your staging API key in all examples below.
Scenario 1: Company Verification sync end-to-end
Submit a company for verification using sync mode and validate the response.
# Submit company verification (sync)
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"company_name": "Test Company Clean Ltd",
"registration_number": "TEST001",
"country": "GB",
"external_reference": "e2e-kyb-sync-001"
}' | jq .
Verify the response contains:
overall_verdict is "Pass", "Refer", or "Fail"
risk_tier is "Low", "Medium", or "High"
company.legal_name is populated
company.verification_status is "Verified"
screening object contains pep_status, sanctions_status, adverse_media_status
checks_summary.total is greater than 0
case_reference starts with "AML-"
# Validate response fields
RESPONSE=$(curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{"company_name":"Test Company Clean Ltd","registration_number":"TEST001","country":"GB","external_reference":"e2e-kyb-sync-001"}')
echo "$RESPONSE" | jq '{
verdict: .overall_verdict,
risk: .risk_tier,
company_verified: .company.verification_status,
checks_total: .checks_summary.total,
pep: .screening.pep_status,
sanctions: .screening.sanctions_status
}'
Scenario 2: Person Verification async end-to-end
Initiate an async Person Verification journey, extract tokens, and poll for results.
# Step 1: Initiate async journey
INIT_RESPONSE=$(curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/init" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-d '{
"first_name": "Test",
"last_name": "Clean",
"date_of_birth": "1990-01-15",
"country": "GB",
"external_reference": "e2e-kyc-async-001"
}')
echo "Init response:"
echo "$INIT_RESPONSE" | jq .
# Step 2: Extract pull token
PULL_TOKEN=$(echo "$INIT_RESPONSE" | jq -r '.tokens.pull')
echo "Pull token: $PULL_TOKEN"
# Step 3: Poll for results (may return 204 while processing)
sleep 10
RESULT=$(curl -s -w "\n%{http_code}" \
"https://instance.staging.onboardapp.io/api/gateway/sharable-payload/$PULL_TOKEN")
HTTP_CODE=$(echo "$RESULT" | tail -1)
BODY=$(echo "$RESULT" | head -n -1)
if [ "$HTTP_CODE" = "200" ]; then
echo "Results ready:"
echo "$BODY" | jq .
elif [ "$HTTP_CODE" = "204" ]; then
echo "Still processing. Poll again in 30 seconds."
elif [ "$HTTP_CODE" = "404" ]; then
echo "Token invalid or expired."
fi
Verify:
- Init response contains
tokens.pull and tokens.start
- Poll returns
204 (processing) or 200 (results ready)
- Final
200 response contains overall_verdict and risk_tier
Scenario 3: Idempotency
Submit the same external_reference twice. The second request should return the existing case, not create a duplicate.
REFERENCE="e2e-idempotent-$(date +%s)"
# First request
echo "--- First request ---"
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d "{
\"company_name\": \"Test Company Clean Ltd\",
\"registration_number\": \"TEST001\",
\"country\": \"GB\",
\"external_reference\": \"$REFERENCE\"
}" | jq '{case_reference: .case_reference, verdict: .overall_verdict}'
# Second request (same external_reference)
echo "--- Second request ---"
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d "{
\"company_name\": \"Test Company Clean Ltd\",
\"registration_number\": \"TEST001\",
\"country\": \"GB\",
\"external_reference\": \"$REFERENCE\"
}" | jq '{case_reference: .case_reference, verdict: .overall_verdict}'
Verify:
- Both responses return the same
case_reference
- No duplicate verification is created
- The second request completes faster (returns cached result)
Scenario 4: Error handling
Test error responses for common failure modes.
Missing required field (400)
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 15000" \
-d '{"country": "GB"}' | jq .
Expected: 400 with error: "VALIDATION_ERROR".
Invalid API key (401)
curl -s -w "\nHTTP %{http_code}" -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: invalid-key-here" \
-H "X-SYNC-TIMEOUT: 15000" \
-d '{"company_name":"Test","registration_number":"TEST001","country":"GB"}'
Expected: 401 with error: "UNAUTHORIZED".
Invalid project hash (404)
curl -s -w "\nHTTP %{http_code}" -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/invalid-hash/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 15000" \
-d '{"company_name":"Test","registration_number":"TEST001","country":"GB"}'
Expected: 404 with error: "NOT_FOUND".
Invalid JSON (400)
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-d 'this is not json' | jq .
Expected: 400 with error: "INVALID_JSON".
Scenario 5: Webhook delivery
Submit a verification and confirm your webhook endpoint receives the event.
Setup
Before running this test, ensure your staging webhook URL is configured. For local development:
# Terminal 1: Start your webhook listener
node -e "
const http = require('http');
const server = http.createServer((req, res) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
console.log('Webhook received:', JSON.stringify(JSON.parse(body), null, 2));
res.writeHead(200);
res.end('OK');
});
});
server.listen(3000, () => console.log('Listening on port 3000'));
"
# Terminal 2: Expose via ngrok
ngrok http 3000
# Note the https URL and configure it as your staging webhook endpoint
Submit verification
# Submit a company verification to trigger a webhook
curl -s -X POST \
"https://instance.staging.onboardapp.io/api/gateway/execute/{project_hash}/api" \
-H "Content-Type: application/json" \
-H "X-API-KEY: staging-key" \
-H "X-SYNC-TIMEOUT: 30000" \
-d '{
"company_name": "Test Company Clean Ltd",
"registration_number": "TEST001",
"country": "GB",
"external_reference": "e2e-webhook-001"
}'
Verify webhook received
Check your webhook listener for a verification.completed event. The payload should include:
event_type: "verification.completed"
callback_reference matching your external_reference
data object with the full verification results
X-Zenoo-Signature header for signature verification
Verify signature
const crypto = require("crypto");
function verifyWebhookSignature(payload, signature, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(payload).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
Checklist
Run all five scenarios and confirm:
Next steps