Katkat

Validate a Third-Party Identity Payload

KikoffSoftware Engineer · Technical Screen

Last updated

Implement a pipeline that validates a third-party identity verification response against schema requirements and business rules while returning sanitized failure reason codes.

Validate a Third-Party Identity Payload

Fintech applications routinely ingest identity verification results from external vendors. Before granting an applicant access to credit products, the system must validate the vendor's payload against strict schema expectations and core business rules.

Write a function validate_identity_payload(payload: dict, blocked_codes: set[str]) -> tuple[bool, str] that processes an incoming identity payload and returns a boolean indicating whether the verification passed, along with a standardized reason string.

Schema Requirements

The payload must be a dictionary with the following top-level keys and types:

  • "status" (str): Must be present.
  • "matches" (dict): Must contain four required keys with boolean values: "name", "dob", "ssn", and "address".
  • "message_codes" (list[str]): A list of vendor-provided status or alert codes.

If the payload is not a dictionary, is missing required keys, or contains invalid types for these fields, return (False, "INVALID_SCHEMA").

Business Rules

Evaluate rules in the following order once the schema is confirmed valid:

  1. Vendor Status: If payload["status"] != "COMPLETE", return (False, "STATUS_INCOMPLETE").
  2. Blocked Codes: If any code in payload["message_codes"] exists in blocked_codes, return (False, "BLOCKED_CODE").
  3. Required Matches: All four match fields (name, dob, ssn, address) in payload["matches"] must be True. If any is False, return (False, "MATCH_FAILED").
  4. Success: If all checks pass, return (True, "SUCCESS").

Interface

def validate_identity_payload(payload: dict, blocked_codes: set[str]) -> tuple[bool, str]:
    pass

Constraints

  • payload will contain at most 50 keys.
  • blocked_codes contains up to 1,000 strings.
  • message_codes contains up to 50 strings.
  • All string comparisons are case-sensitive.

Examples

Example 1

payload = {
    "status": "COMPLETE",
    "matches": {
        "name": True,
        "dob": True,
        "ssn": True,
        "address": True
    },
    "message_codes": ["ADDR_CONFIRMED", "PHONE_VERIFIED"]
}
blocked_codes = {"FRAUD_ALERT", "DECEASED"}

# Output: (True, "SUCCESS")

Explanation: Schema is intact, status is complete, no blocked codes are present, and all four matches are True.

Example 2

payload = {
    "status": "COMPLETE",
    "matches": {
        "name": True,
        "dob": True,
        "ssn": True,
        "address": False
    },
    "message_codes": ["FRAUD_ALERT"]
}
blocked_codes = {"FRAUD_ALERT", "DECEASED"}

# Output: (False, "BLOCKED_CODE")

Explanation: Even though address matching failed, the blocked code rule is evaluated first.

Hints

Hint 1

Separate your validation into distinct phases: schema/type checking, vendor completion checking, security/risk filtering, and attribute matching.

Hint 2

Verify not only that the required keys exist, but also that their nested types (such as booleans for matches and strings for message codes) are correct before inspecting values.

Hint 3

Use a set lookup for blocked code detection to keep the filtering step running in O(K) time, where K is the number of message codes in the payload.

Follow-up questions

  • How would you refactor this function to collect and return all failed validation reasons rather than short-circuiting on the first failure?
  • How would you support schema versioning if different identity providers returned distinct payload structures?
  • How can you ensure sensitive applicant data (PII) is safely masked if you need to log failed validation payloads for debugging?