Verifications
A verification is a record of a check Kontorion ran (or had a third-party run) against a customer - a VIES VAT lookup, a KYC document review, a payment-instrument ownership confirmation. Verifications are first-class objects with their own typed status so downstream features (reverse-charge eligibility, promotion conditions, payment-method gating) can read directly from them.
Anatomy
| Field | Notes |
|---|---|
customer_id | The customer this verification belongs to |
check_type | Optional closed enum classifying the check: one of VAT / IDENTITY / KYC / SANCTIONS. Used for feature gating and per-type enablement; distinct from the free-form verification_type |
provider | Free-form string identifying the external system that ran the check (vies, stripe_identity, manual, etc.). Provider-specific |
verification_type | Free-form string for the kind of check (eu_vat, business_registration, identity_document, bank_account_ownership, …). Provider-defined; treat unknown values as opaque |
status | One of pending / verified / expired / failed / manual / maintenance |
verified_at | When the most recent passing check completed |
expires_at | When the verification's verified window ends (provider-set or org-policy) |
provider_reference | Provider's own ID for the underlying check (the VIES query record, the Stripe Verification Session ID, etc.) - useful for follow-up calls and audit |
last_checked_at | When the engine last polled / re-ran the check |
reason | Free-form note explaining the current status - set on manual overrides and on deferred/failed outcomes (e.g. "inside configured maintenance window", "VIES unavailable after retries") |
Both provider and verification_type are free-form strings, not closed enums - Kontorion doesn't try to be the source of truth for which providers and check types exist. Define the values that match the providers you've wired up.
Status lifecycle
Code
| Status | Meaning |
|---|---|
pending | The check has been started but the result hasn't come back yet - typical for async providers (VIES, KYC) |
verified | The provider returned a passing result. verified_at is set; expires_at may be set if the provider declared one |
expired | A previously-verified check passed its expires_at. The customer needs re-verification before the verified state unlocks again |
failed | The provider returned a definitive negative |
manual | A human operator asserted the result out-of-band (e.g. a VAT manual override). Treated as an authoritative pass by consumers; carries a reason and may carry an expires_at |
maintenance | The check could not be run and was deferred - the provider was unavailable or the org is inside a configured maintenance window (e.g. VIES degraded after retries). Not a negative result; the check is re-run later |
Allowed transitions:
| From | To |
|---|---|
pending | verified, failed, manual, maintenance |
verified | expired, failed, manual |
expired | pending (kick off re-verification) |
failed | pending (retry), manual |
manual | pending, expired |
maintenance | pending (re-run once the provider is back) |
The transition rules are enforced - invalid transitions return 409 INVALID_TRANSITION.
Verifications don't currently emit dedicated webhook events. To react to a status change, poll the verification or watch the consuming feature's events (e.g. an
eu_vatgoingexpiredwill surface as a tax-treatment change on the next invoice).
What verifications gate
Other features read verification status; nothing in the verifications surface itself enforces gating. Common consumers:
| Consumer | Reads |
|---|---|
| Tax engine | An eu_vat verification at status verified is the precondition for intra-EU B2B reverse-charge treatment. expired or failed falls back to the consumer (B2C) path |
| Promotions | The external_verification condition matches against the named verification type + status |
| Your application | Read GET /v1/verifications?customer_id=... and gate plan tiers, payment-method activation, etc. however your business rules dictate |
Creating
Code
The request body is intentionally small - no provider-specific input fields. The provider knows where to find what it needs (e.g. the VIES provider reads the customer's tax_identifiers["eu_vat"]). The response carries the verification ID and initial status (pending for async providers, the final state for synchronous ones).
For the EU VAT case specifically, you can also kick off a check via POST /v1/customers/{id}/verifications/vat - it's a thin wrapper that creates an eu_vat verification using the customer's primary VAT ID.
Updating status
For external type and manual reviews, your application or the dashboard reports the outcome via PUT (not POST) on the status endpoint:
Code
The request body accepts only status (one of pending / verified / expired / failed / manual / maintenance). The transition rules above apply - the engine refuses to set, e.g., verified → pending directly.
For provider-driven status updates, the integrating service (the VIES poller, the KYC webhook handler) owns the lifecycle - you don't manually flip status when a provider's automated check completes.
Endpoints
All Verifications endpoints - GET /verifications (cursor-paginated, supports ?customer_id= filter), GET /verifications/{id}, POST /verifications, PUT /verifications/{id}/status.
Related
- Customers - owns the verifications and the
tax_identifiersproviders read from - Taxes - reverse-charge gating against
eu_vatstatus - Promotions - the
external_verificationcondition type - Payments - payment-method activation typically reads the relevant ownership / KYC verification