Skip to content

Verification Federation API (v2)

The Verification Federation API allows a tenant who runs an upstream identity provider (a bank’s online banking session, a government eID, a telecom’s pre-enrolled authenticator) to mint a federation token that the platform validates and treats as a verified factor, bypassing the platform’s own KBV flow.

The API is the strict-conformance path for NIST SP 800-63B §5.1.3 (pre-enrolled out-of-band authenticators). Tenants without a federation provider use the platform’s email magic-link possession factor by default; that path is aligned with §5.1.3’s principles but does not meet the literal “pre-enrolled” requirement.

The contract below is fixed. The implementation lands in v1.1 of the identity rework. v1 ships everything except the federation acceptance path; this page lets enterprise prospects evaluate whether the conformance route exists.

  1. Tenant registers a trust anchor in their workspace settings: an asymmetric public key (RSA-PSS or Ed25519), an issuer string, and a list of factor_type values the issuer is permitted to assert.
  2. Tenant’s identity provider issues a federation assertion to the data subject (a user-of-tenant). The assertion is a signed JWT-shaped token whose claims describe the principal, the factor type minted, and the issuer.
  3. Tenant’s customer-facing application redirects the data subject’s session into the oHallo platform (chat-widget bootstrap, voice callback, or inbound email subscription URL) with the federation assertion attached.
  4. The platform validates the assertion against the workspace’s trust anchor and mints the corresponding Factor record. The caller is now identified (one factor) or high-assurance (combined with another factor of a different category) without running KBV.

A trust anchor declares which issuer the platform is willing to accept assertions from and what factors that issuer can vouch for.

PUT /api/workspaces/{workspace_id}/verification-trust-anchors/{anchor_id}
Authorization: Bearer sf_live_v1_...
Content-Type: application/json
{
"issuer": "https://identity.acme-bank.example/",
"algorithm": "EdDSA",
"public_key_pem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
"permitted_factor_types": ["upstream_attested", "webauthn", "push_app"],
"max_assertion_age_seconds": 300,
"audience": "https://platform.ohallo.eu/"
}

Returns 201 on creation, 200 on update. The trust anchor is durable and applies to all subsequent verification flows for the workspace.

The assertion is a signed JWT with these required claims:

{
"iss": "https://identity.acme-bank.example/",
"sub": "contact-uuid-as-known-to-platform",
"aud": "https://platform.ohallo.eu/",
"iat": 1715520000,
"exp": 1715520300,
"factor_type": "upstream_attested",
"factor_category": "possession",
"level_of_assurance": "ial2"
}
  • sub MUST match a contact_id known to the workspace. The tenant is responsible for mapping their internal user id to the platform’s contact id (typically at contact create time via the external_id field).
  • factor_type and factor_category MUST be in permitted_factor_types on the trust anchor.
  • exp - iat MUST NOT exceed max_assertion_age_seconds.
  • aud MUST match the platform audience configured on the anchor.

Signature is verified using the trust anchor’s algorithm + public_key_pem. Any failure path (bad signature, expired, unknown issuer, audience mismatch, factor not permitted, contact not found) returns a typed deny without minting a factor.

The assertion travels via one of three channels:

The tenant’s bootstrap call to the chat-widget script includes the assertion in the oHallo.start({ ..., federationAssertion: '<jwt>' }) call. The widget forwards it to the platform on the first message.

For voice callers, the tenant’s customer-facing application embeds the assertion in a one-time callback URL that triggers an outbound call from the platform. The tenant calls:

POST /api/conversations/voice/dispatch
Authorization: Bearer sf_live_v1_...
Content-Type: application/json
{
"callee_phone": "+15551234567",
"workspace_id": "...",
"federation_assertion": "<jwt>"
}

The platform dials the callee, and the assertion is associated with the resulting conversation. The verification specialist sees the caller as already identified on session start.

For email, the tenant includes the assertion as a query parameter on the platform-issued conversation subscription URL. The first inbound email on that URL is associated with the asserted contact.

  • One assertion per conversation. Re-presenting different assertions inside the same conversation is rejected.
  • No revocation channel in v2. The assertion lifetime is governed solely by exp. If the tenant needs to revoke an assertion mid-conversation, they trigger a fresh verification flow on the platform side via the v1 DSR / scope-management surface.
  • Audit logging. Every accepted assertion writes a factor_added identity_event with evidence_ref: federation:{anchor_id}:{jti}. The jti claim, if present in the assertion, is captured for non-replay analysis.

§5.1.3 defines out-of-band authenticators as devices the user has separately enrolled with a trusted issuer. The platform’s email magic-link is a close substitute — the user has demonstrably enrolled their email address as a recovery channel — but it is not a pre-enrolled authenticator in the §5.1.3 sense. Federation closes that gap: the tenant’s identity provider IS the pre-enrolling party.

This matters for tenants whose regulators (FFIEC, EBA, MAS) require literal §5.1.3 conformance rather than the principle-aligned signal the magic-link gives. Federation gets you there.