Skip to content
SPEC-ID-0003SpecificationIn forceversion 1.0.02 October 2026

In brief ​

This document describes how an institution verifies, before issuing a credential, that a person really is who they claim to be (kimlik doğrulama (identity proofing)Belge verilmeden önce kişinin gerçekten o kişi olduğunun doğrulanması; örneğin kimlik kartı ve canlılık testiyle.). It is written for issuing institutions (issuersBelgeyi imzalayıp veren kurum: üniversite, meslek kuruluşu, kamu kurumu ya da şirket.) and for developers who connect to an identity verification provider.

When to read

  • First read the Issuance concept page and the GUIDE-0003 guide.
  • How the credential is bound to the wallet is not covered here: SPEC-CRED-0002.
  • For Tamga's identity credential service, go straight to §9.

Plain explanation

Every credential type requires a minimum assurance level (LoA (Level of Assurance)Bir kimliğe ya da belgeye ne kadar güvenilebileceği; Tamga kimlik doğrulama (T), belge veren (I) ve cüzdan (W) için ayrı seviyeler kullanır.): for a student certificate, e-mail and SMS are enough; a diploma needs stronger verification (for example an identity card + liveness check + face match, or the institution's registration desk). The institution reaches this level before issuing the credential; if it cannot, it does not issue. Remote verification is performed by a licensed provider; the institution keeps only a short summary of the result, identity images are not stored and the level is not written into the credential. Until a state identity credential exists, Tamga's identity service performs this verification and gives the result to the wallet as a separate identity credential.


Scope ​

This specification covers identity proofing on the institution's side: how the question "is the person receiving this offer really the subject of the credential?" is answered before issuance. Out of scope: binding to the wallet (cnf/KB-JWT (Key Binding JWT)Cüzdanın gösterme anında belge anahtarıyla imzaladığı kısa belirteç; anahtarın belge sahibinde olduğunu kanıtlar. — SPEC-CRED-0002), face matching on the doğrulayıcı (verifier)Gösterilen belgeyi denetleyen taraf: imza, belge verenin güven listesindeki kaydı, durum ve politika. Relying party diye de anılır. side (the RP (Relying Party)Relying party'nin kısaltması: belge isteyen ve doğrulayan kuruluş.'s responsibility), and PID (Person Identification Data)Devletin en yüksek güvence seviyesinde verdiği temel kimlik verisi. Tamga bu rolü üstlenmez; Tamga'nın kimlik belgesi PID değildir. issuance (Tamga does not issue PID, BT8).

Principle (PM-ASSUR-0001; the eIDAS (electronic IDentification, Authentication and trust Services)AB'nin elektronik kimlik ve güven hizmetleri tüzüğü; eIDAS 2.0 Avrupa Dijital Kimlik Cüzdanı'nı getirir. model): the belge sahibi (holder)Belgeyi cüzdanında tutan ve kime göstereceğine karar veren kişi. level is not written into the belge (credential)Belge verenin imzaladığı ve kişinin cüzdanında duran dijital belge; kişi yalnız istenen alanları gösterir.; a credential type carries a minimum level as a precondition; the institution reaches that level before issuance and writes the binding path into its audit record (SPEC-PROTO-0001/PR7).


1. Levels and paths ​

LeveleIDAS LoAETSI TS 119 461 (D-ASSUR-2)PathWhere
T0——Device key + e-mail/phone OTP; no identity claimAnonymous/pseudonym scenarios (not used in beta)
T1LowBaseline(a) e-mail offer + SMS tx_code over a different channel; (b) offer on the student information system screen (single factor); (c) bank micro-transfer / mobile line ownershipIssuer/portal
T2SubstantialSubstantial (document + liveness or in person)(a) Licensed remote identity verification (identity document OCR/NFC + liveness + face match) — "IDV provider" in this document; (b) offer on the student information system screen with MFA; (c) the institution's registration desk (in person, staff confirmation)IDV provider / portal / desk
T3HighHighSigning a nonce with a qualified e-signature / mobile signature (from the initial stage on); PID in the state stageOnly authorization code or in person; forbidden with pre-authorized (ETSI TS 119 472-3 GEN-REQ-4.1)

Type ↔ minimum level is set in the rulebooksBağlayıcı kurallar seti. Tamga Rulebook tüm katılımcıların kurallarını taşır; her belge türünün ondan dallanan kendi rulebook'u vardır.: Education Rulebook — student certificate T1, diploma T2 (FW-RB-0002 §4). If the institution cannot reach the level a type requires, it does not produce an offer (issuance stops; no guessing or upgrading).


2. Flow — where identity proofing sits in the issuance chain ​

Portal / SIS session (person)                    Issuer service
   │  "Add my diploma to my wallet"                 │
   ├── type = Diploma → required level T2 ─────────▶│ current level? (session type, earlier proofing record ≤ 12 months)
   │                                                  │
   │  ◀── if insufficient: start IDV session (T2a) ──┤  POST /v3/session  → url
   │  IDV provider page: document + selfie            │
   │  ──▶ completed (callback / webhook) ────────────▶│ decision → T level + record
   │  ◀── offer (QR + tx_code)  ──────────────────────┤ PR7: audit {path:"idv:<provider>", level:"T2", session_ref}
   ▼                                                  ▼
Wallet OpenID4VCI (SPEC-PROTO-0001) — unchanged

Identity proofing finishes before the offer is produced; the offer itself and the tx_code rules do not change (PR1, PR3, DB-5). The result may be reused for the same person for 12 months (PROPOSAL); the assurance decay rules apply (PM-ASSUR-0001 §Decay).


3. Remote identity verification provider — general rules ​

#Rule
IP1The provider's result is evaluated only on the issuer's side; the wallet and the verifier do not talk to the provider.
IP2The issuer binds the provider session to its own subject: vendor_data = an opaque issuer-internal reference (a random token of the student record); the identity number, name and e-mail are not written into vendor_data.
IP3The expected identity (given name, family name, date of birth) may be given to the provider as expected_details; the provider performs the match; the issuer additionally compares the result with the source data (exact match of name + date of birth; a difference → Review).
IP4What the issuer's system keeps is a summary of the result: {provider, session_ref, level, decided_at, checks:[id_document, liveness, face_match], expires_at}. The document image, portrait, raw OCR data and video are not stored (FW-RB-0001 RB-AP-23).
IP5Retention on the provider's side is reduced to the minimum by contract; the privacy notice under the Turkish data protection law (KVKK) names the provider as a data processor.
IP6A webhook MUST be verified with its signature + timestamp; an unsigned or stale (> 300 s) webhook is rejected; deduplication by event_id.
IP7A webhook is only a trigger; the decision is always confirmed by fetching it from the provider's decision API (the webhook body is not trusted).
IP8"In Review" / "Declined" / "Expired" → no offer is produced; the person is not shown the provider's result but the "apply to the institution's registration desk" path. The reason for rejection is not passed to the person in the provider's wording.
IP9During a provider outage, diploma issuance stops (it is not downgraded to T1); the student certificate (T1) is not affected.
IP10The provider and the workflow identifier (workflow_id) are in the tenant configuration; the code is not provider-specific but works through an adapter interface (IdvProvider — §6).
IP11Logs: event type + session_ref + level; the person's name, document number and scores are not written (in the spirit of AP3).

4. Reference provider: Didit (v3 API) — protocol mapping ​

Source: docs.didit.me (checked on 2026-09-24). It may change; the adapter is versioned.

StepDiditTamga usage
Authenticationx-api-key: <API_KEY> (Business Console → Application → API & Webhooks)Tenant secret (TAMGA_IDV_DIDIT_API_KEY); in .env, never in the repository
Create sessionPOST https://verification.didit.me/v3/session/ body: workflow_id (required), vendor_data, callback, callback_method, metadata, language, contact_details, expected_details {first_name,last_name,date_of_birth,id_country,expected_document_types}vendor_data = <opaque subject_ref>; callback = https://id.tamga.network/idv/return?r=<flow>; language = tr; expected_details name/surname/date of birth (IP3); metadata empty
Responsesession_id, session_token, url, status: "Not Started", workflow_versionRedirect to url; session_id = session_ref
ResultGET https://verification.didit.me/v3/session/{sessionId}/decision/ → status, features[], id_verifications[] {status, first_name, last_name, date_of_birth, document_type, issuing_state, expiration_date, verification_method, assurance, …}, liveness_checks[] {status, score}, face_matches[] {status, score}, nfc_verifications[], aml_screenings[]Only status, the status values of the sub-checks and the name/surname/date-of-birth match result (boolean) are kept; image fields (portrait_image, front_image, back_image, video_url) are read but not stored
WebhookPOST body: event_id, webhook_type (status.updated …), session_id, status, vendor_data, decision, timestamp, created_at, environment; headers X-Signature-V2 (HMAC-SHA256, canonical JSON), X-Signature (raw body), X-TimestampX-Signature (raw body) + X-Timestamp (±300 s) are verified; deduplication by event_id; then confirmation through the decision API (IP7). Retries: ~1 min, ~4 min; 5 s timeout → the endpoint returns 200 quickly and queues the work
StatesNot Started, In Progress, Awaiting User, In Review, Approved, Declined, Resubmitted, Expired, Kyc Expired, AbandonedMapping in §5
WorkflowNo-code workflow in the Business Console: ID + liveness + face match (+ optional NFC)Tamga workflow: ID document + passive liveness + face match; AML off (purpose limitation); phone/e-mail off (T1 already uses SMS)
ChannelHosted URL (redirect), iframe, mobile SDK (iOS/Android/RN)Today (list stage): hosted URL (through the identity service id.tamga.network, desktop/mobile browser). The in-wallet SDK is not used (identity proofing is the institution's job, not the wallet's)
Sandboxsandbox_scenario field; environment: "sandbox"Demo: with sandbox scenarios; a fake adapter in end-to-end tests
Price500 verifications/month free; per use afterwardsThe pilot scale fits in the free quota

5. Result → level mapping ​

Didit statusSub-checksName/surname/date-of-birth matchTamga result
Approvedid_verifications[].status = Approved ∧ liveness_checks[].status = Approved ∧ face_matches[].status = ApprovedExact matchT2 (path: "idv:didit")
ApprovedNFC Approved (chip document)ExactT2 (note: nfc: true; not T3 — T3 requires a signature)
Approvedany of them not Approved—Insufficient → desk
Approvedall ApprovedNo matchReview (staff decision; no automatic T2)
In Review——Wait (≤ 24 h); then staff
Declined, Expired, Kyc Expired, Abandoned——Insufficient; new session ≤ 3 attempts / 24 h
Not Started, In Progress, Awaiting User, Resubmitted——In progress

Document type restriction: expected_document_types = ["ID", "P"] (Turkish identity card, passport); issuing_state must be consistent with the institution's country or the student's nationality (if the nationality is not in the source data, the restriction is not applied).


6. Adapter interface (implementation contract) ​

ts
interface IdvProvider {
  readonly id: "didit" | string;
  createSession(input: {
    subjectRef: string;                 // opaque, not personal data
    expected: { givenName: string; familyName: string; birthDate: string };
    locale: "tr" | "en";
    returnUrl: string;
  }): Promise<{ sessionRef: string; url: string; expiresAt: number }>;
  fetchDecision(sessionRef: string): Promise<IdvDecision>;
  verifyWebhook(rawBody: Uint8Array, headers: Record<string, string>): { ok: boolean; eventId?: string; sessionRef?: string };
}
type IdvDecision =
  | { state: "PENDING" }
  | { state: "REVIEW" }
  | { state: "FAILED"; reason: "declined" | "expired" | "abandoned" | "mismatch" | "incomplete" }
  | { state: "PASSED"; level: "T2"; checks: { idDocument: true; liveness: true; faceMatch: true; nfc?: boolean }; decidedAt: number; expiresAt: number };

Stored record (issuing service data/<slug>/state.json › proofing[subjectRef]): { provider, sessionRef, level, checks, decidedAt, expiresAt } — no other fields.


7. Error and boundary behaviour ​

SituationBehaviour
Provider 5xx / timeoutNo diploma offer is produced; the person sees "identity verification is not possible right now" + the desk option
Invalid webhook signature400; event log entry; no action
Webhook did not arriveThe portal's return page queries the decision API (polling ≤ 10 min, 15 s interval)
Person fails 3 times24-hour lock; desk path
Result older than 12 monthsProof again (decay)
Registration desk pathStaff panel: "I have seen the identity" confirmation → path: "desk", level: T2, staff identity in the audit record

8. Invariants ​

#Invariant
IDP1The holder's identity proofing level and path are not written into the credential; they are kept only in the issuer's audit record.
IDP2No offer is produced before the type's minimum level is reached; the level is not upgraded by guessing.
IDP3The IDV provider integration lives only in the Tamga identity attestation service (id.tamga.network, ADR-0011); institutional issuers, the wallet and the verifier do not talk to the provider.
IDP4From the IDV result the issuer keeps only the summary record; document images, portraits, video and raw OCR data are not stored.
IDP5A webhook is not processed before its signature and timestamp are verified; the decision is always confirmed through the decision API.
IDP6No personal data is written into the vendor_data/metadata fields; only an opaque reference.
IDP7T3 is never granted on the basis of a remote IDV result; T3 requires a signature (qualified e-signature/mobile signature) or a PID and is not compatible with the pre-authorized flow.
IDP8A provider outage does not lower the level; issuance that needs it stops.
IDP9The identity service keeps the personal fields from the IDV result only until the moment of issuance; after issuance only the opaque subject_ref, the document number hash, the validity period and the status indices remain; images, selfies, video and raw OCR data are never stored at Tamga (K4).
IDP10The national identity number (personal_administrative_number) is carried only in the urn:tamga:id:IdentityAttestation:1 type and only as a selectively disclosable claim; it is written into no other type; the institution does not keep or log it after matching.
IDP11Before the identity attestation is issued, the privacy notice is shown and explicit consent is obtained; in a session without consent no IDV is started (access_denied).

9. Tamga identity credential service ​

Until a state PID provider is appointed, remote identity verification is performed in Tamga's identity service and the result is given to the wallet as a credential. This credential is not a PID but a non-qualified EAA (Electronic Attestation of Attributes)Kişinin bir özniteliğini (diploma, üyelik gibi) doğrulayan belge için eIDAS'taki ad. issued by Tamga as an issuer (category: IDENTITY, class: EAA, I2; no category claim in the credential — ADR-0022); pid_providers[] stays empty (TL8).

ItemValue
vcturn:tamga:id:IdentityAttestation:1 (catalogue: id/IdentityAttestation/1.0.0)
Claimsgiven_name, family_name, birth_date, nationality, personal_administrative_number (Turkish ID number), document_type, document_number_hash (keyed SHA-256 / HMAC; 1.0.1), issuing_country, document_chip_verified, verification_method (remote-document-liveness-face | remote-nfc-liveness-face | in-person), age_over_18 — all selectively disclosable; no portrait; no LoA claim (PR7)
Validity / statusexp = issuance + 730 days; Token Status List (id.tamga.network/status/{listId}); when the same document is verified again, the previous one is revoked (K6)
FlowWallet: PAR (WUA) → browser /authorize (KVKK privacy notice + explicit consent) → provider (Didit v3; demo: FAKE, deviation S-15) → /idv/return decision query (the webhook is only a trigger, IDP5) → code → token (PKCE + WUA) → 10 copies
Use at an institutionThe institution's issuing service requests a presentation of the identity credential when issuing (DCQL: Turkish ID number, date of birth, given name, family name), verifies T0 + A–E and matches it with its records (Turkish ID number + date of birth; a warning on normalised names) — SPEC-PROTO-0001 §11.2
LevelRemote document + liveness + face = T2 (ETSI 119 461 Substantial); NFC chip = document_chip_verified: true (technically High; legally T3 = qualified e-signature/mobile signature, IDP7)
Data controllerTamga Network (K4): it keeps the record + hash; erasure request = §9.1 (the record is deleted, copies are revoked, images are deleted at the provider); the provider contract allows ≤ 30 days of image retention
WebhookA single target https://id.tamga.network/idv/webhook, event status.updated; there is no per-institution webhook

Implementation: apps/id (operator repository) (config/didit/store/app); the IdvProvider interface is the same as in §6 (DiditProvider, FakeIdvProvider).

9.1 The person's erasure request — POST /erasure ​

There is no user account; the request is proven by presenting the credentials the service has issued.

ItemValue
Request{ "presentations": [ SD-JWT VC + KB-JWT, … ] } (1–20); no claim is disclosed; KB-JWT aud = the service address, nonce = POST /nonce (single use)
VerificationThe signature is this service's (issuerId); possession through the KB-JWT cnf key; the record is found from the status list index
EffectAll copies of the record become INVALID; the record and the event log rows tied to it are deleted; the provider session is deleted with privacy_erasure (IdvProvider.deleteSession)
Response`{ erased, rejected, provider: "deleted"
LogOnly counts (erasure: number of records, provider result); the record identifier and subject_ref are not written

Only bits remain in the iptal listesi (status list)Her belgenin tek bir konumu olduğu sıkıştırılmış, imzalı liste; geçerli, askıda ya da iptal olduğunu söyler. (no personal data). The takma ad (pseudonym)Cüzdanın her web sitesi için ayrı türettiği kararlı hesap kimliği; iki site aynı kişiyi eşleştiremez. seed is not stored on the service side, so there is nothing to delete (PS2); if the person verifies their identity again, the same seed is derived again (ADR-0031).

9.2 App-store review — single-use review code (ADR-0033) ​

ItemValue
CodeGenerated by the operator (ops/review-code.ts create); 26 characters (130 bits), at most 14 days, single use, at most 3 active at the same time; only an HMAC digest in the database (review_codes; RV1)
EntryThe "Review code" field on the /authorize privacy notice page (POST /authorize/consent, review_code); if empty, the flow does not change
EffectWith a valid code, a trial verification only for that PAR (/review-idv/{session}, no request to the real provider); the person is a code-specific DEMO person (given name "DEMO", family name "App Reviewer")
SignatureA separate DEMO signer tamga-id-review (I1 in the trust list); the real signer is not used (RV2). The status list is the identity service's list
Credentialverification_method: review-demo, at most 7 days; the pseudonym seed comes from a separate key (it does not collide with real seeds)
AcceptanceIt passes no policy that requires I2; only the Tamga Verify review-* policies (I1)
Limits5 failed attempts per PAR; service-wide, 20 failures in 10 minutes lock it for 15 minutes (IP addresses are not used); nginx rate limit on /authorize/consent
OffIf there is no DEMO key or the trust list does not know tamga-id-review, entering a code returns "not active" (503)
Logreview_code.accepted / .used (code identifier), .rejected (no detail); never the code or its digest

Security and privacy notes ​

  • Identity proofing is purpose-limited: it is only a precondition of issuance; AML/PEP screening is off.
  • The provider sees the person's document and face; this is stated clearly in the KVKK privacy notice and the choice of provider (data location, retention period, sub-processors) is bound by contract.
  • expected_details gives the provider the name/surname/date of birth (from the source data); this improves match quality but is a data disclosure; the alternative (matching the name returned by the provider at the institution) is defined in IP3 and left to the tenant's preference (PROPOSAL: do not share by default).
  • The demo uses the sandbox; no real personal data is processed (GT1).

Open questions ​

  1. Result reuse period (12 months) and attempt limits — the numbers are PROPOSALS.
  2. In-wallet IDV (SDK) never? It becomes unnecessary with the PID in the state stage; not for now.
  3. Provider diversity: a second adapter (a domestic provider / e-Devlet) — a vendor lock-in tripwire.

Related documents ​

PM-ASSUR-0001 · PM-ID-0001 · SPEC-PROTO-0001 · SPEC-CRED-0002 · SPEC-WALLET-0001 · FW-RB-0001 · FW-RB-0002 · ADR-0005 · ADR-0011 · ADR-0022

Status ​

In force — version 1.0.0 (2026-10-02).