> For the complete documentation index, see [llms.txt](https://docs.facephi.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.facephi.com/docs.facephi-pt-br/produtos/idv-suite/flujos-and-integraciones/configuracion-tecnica-del-cliente/webhook.md).

# Webhook

## Documentação de Webhooks

### Objetivo

Os webhooks permitem que seu sistema receba notificações em tempo real à medida que uma operação de verificação avança:

* início da operação,
* capturas de evidência (documento, selfie, NFC),
* avaliações biométricas,
* resultado final.

Com isso, você pode atualizar estados de negócio, disparar regras e mostrar rastreabilidade ao usuário final sem consultar continuamente por polling.

### Como funciona o envio

1. Você configura uma URL HTTPS de recepção na sua integração.
2. O sistema envia eventos por `POST` com payload JSON.
3. Só são enviados os eventos aos quais você se inscreveu na sua configuração.
4. Cada mensagem inclui assinatura para validar a autenticidade.

Recomendação para seu endpoint:

* responder rapidamente com `2xx` quando a mensagem for aceita,
* processar de forma assíncrona quando possível,
* lidar com idempotência usando `id` do evento.

### Segurança e autenticidade

Cada webhook inclui:

* cabeçalho `Content-Type: application/json`,
* cabeçalhos de segurança personalizados (se você os definiu),
* campo `signature` dentro do payload.

A assinatura é calculada com HMAC SHA-256 sobre o conteúdo do evento e uma chave compartilhada de integração. Você deve validar essa assinatura antes de considerar a mensagem como confiável.

#### Como é calculado `signature`

1. Toma-se o evento **sem** o campo `signature`.
2. É serializado com [JSON Canonicalization Scheme (JCS, RFC 8785)](https://www.rfc-editor.org/rfc/rfc8785).
3. Aplica-se HMAC-SHA256 do JSON canônico.
4. O digest é codificado em **base64**.

O payload é entregue já serializado em JCS (incluindo `signature`), para que o body coincida com o contrato de assinatura.

#### Como verificá-la

Passos recomendados (robustos em qualquer linguagem):

1. Fazer o parse do JSON recebido.
2. Ler e guardar `signature`.
3. Remover `signature` do objeto.
4. Canonicalizar o objeto restante com JCS (RFC 8785).
5. Calcular `HMAC-SHA256` + base64 com a mesma chave compartilhada de integração.
6. Comparar em tempo constante com a assinatura recebida.

Se você consumir o body bruto exatamente como está, em runtimes que preservam a ordem das keys ao fazer parse/serialização, geralmente basta remover `signature` antes do HMAC. Ainda assim, a verificação formal documentada é **JCS + HMAC-SHA256 + base64**.

### Estrutura geral do webhook

Todos os eventos seguem a mesma estrutura base. O body é entregue em ordem JCS:

```json
{
	"data": {},
	"id": "uuid-del-evento",
	"signature": "firma-hmac-base64"
	"source": "/operations/{operationId}",
	"specversion": "1.0",
	"time": "2026-03-06T12:00:00.000Z",
	"type": "com.idv_suite.api.workflows.algo.v1"
}
```

Significado dos campos:

* `id`: identificador único da mensagem (usar para evitar duplicados).
* `type`: tipo de evento (define como interpretar `data`).
* `source`: operação de origem do evento.
* `time`: data/hora UTC de emissão.
* `data`: conteúdo de negócio do evento.
* `signature`: assinatura da mensagem (HMAC-SHA256 em base64 sobre o evento canônico sem este campo).

### Base TypeScript

Todos os eventos compartilham este envelope base:

```ts
type WebhookSource = `/operations/${string}`;

type WebhookEnvelope<TType extends string, TData> = {
	specversion: '1.0';
	id: string;
	type: TType;
	source: WebhookSource;
	time: string;
	data: TData;
	signature: string;
};
```

Notas de modelagem:

* `specversion` hoje é sempre `"1.0"`.
* `id`, `time` e `signature` não têm catálogo fechado.
* `source` sempre segue o padrão `/operations/{operationId}`.
* Em cada evento, são fechados como enum apenas os valores que a implementação atual define explicitamente.
* Quando um provedor externo ou uma etapa interna não define um catálogo fechado, o campo deve ser deixado como `string`, `Record<string, unknown>` ou uma estrutura aberta equivalente.

### Eventos atualmente disponíveis

#### 1) Operação iniciada

* **Tipo**: `com.idv_suite.api.workflows.operation_started.v1`
* **Quando é enviado**: ao criar/iniciar uma operação.
* **Para que serve**: abrir o acompanhamento da operação e correlacionar com seus sistemas.

Tipagem TypeScript:

```ts
type OperationStartedWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.operation_started.v1',
	{
		sessionId: string;
		authenticationId?: string;
		customerId: string;
		source: string;
		document?: {
			type?: string;
			number?: string;
			issuer?: string;
			gender?: string;
			code?: string;
			name?: string;
			surname?: string;
		};
	}
>;
```

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "4f65587f-2c4f-4ea2-b7ff-8fbf7d6fe8e8",
	"type": "com.idv_suite.api.workflows.operation_started.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:00:00.000Z",
	"data": {
		"customerId": "user-123",
		"sessionId": "sess-789",
		"source": "sdk.mobile"
	},
	"signature": "firma-hmac-base64"
}
```

#### 2) Consentimento de termos

* **Tipo**: `com.idv_suite.api.workflows.terms_consent.v1`
* **Quando é enviado**: ao aceitar ou rejeitar termos.
* **Para que serve**: rastreabilidade legal e regras de continuidade do fluxo.

Tipagem TypeScript:

```ts
type TermsConsentWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.terms_consent.v1',
	{
		stepId: string;
		success: boolean;
		timestamp: string;
		accepted: 'accepted' | 'rejected';
	}
>;
```

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "18a26cf2-4556-4f69-9be5-e0b677f4de82",
	"type": "com.idv_suite.api.workflows.terms_consent.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:00:15.000Z",
	"data": {
		"stepId": "1d80cdd1-9dfb-4ef3-b0aa-530f2db60a02",
		"success": true,
		"timestamp": "2026-03-06T12:00:14.000Z",
		"accepted": "accepted"
	},
	"signature": "firma-hmac-base64"
}
```

#### 3) Documento capturado (ID/OCR)

* **Tipo**: `com.idv_suite.api.workflows.id_captured.v1`
* **Quando é enviado**: ao concluir a captura e a extração de dados do documento.
* **Para que serve**: preencher dados documentais e validar consistência.

Tipagem TypeScript:

```ts
type IdCapturedWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.id_captured.v1',
	{
		stepId: string;
		success: boolean;
		data: Record<string, unknown>;
		summary: Record<string, unknown>;
		assets?: string[];
	}
>;
```

Notas:

* `data` e `summary` não têm um schema fechado na implementação atual.
* `assets` contém ids de assets associados ao evento.

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "426133f2-50ea-4f4c-8fe8-5435286ea737",
	"type": "com.idv_suite.api.workflows.id_captured.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:01:50.000Z",
	"data": {
		"stepId": "7f4af246-d48c-4ce6-b3b1-bdf65ba7c9dd",
		"success": true,
		"data": {
			"documentNumber": "X1234567"
		},
		"summary": {
			"name": "JUAN",
			"surname": "MARTINEZ"
		},
		"assets": ["asset-portrait-id", "asset-signature-id"]
	},
	"signature": "firma-hmac-base64"
}
```

#### 4) Selfie capturada

* **Tipo**: `com.idv_suite.api.workflows.selfie_captured.v1`
* **Quando é enviado**: ao ser concluída a captura facial.
* **Para que serve**: marcar avanço da etapa biométrica.

Tipagem TypeScript:

```ts
type SelfieCapturedWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.selfie_captured.v1',
	{
		stepId: string;
	}
>;
```

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "f0d3da62-0711-4b50-93f8-5034b4d4a4bb",
	"type": "com.idv_suite.api.workflows.selfie_captured.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:01:02.000Z",
	"data": {
		"stepId": "ab4b360f-1f6b-4ca4-ae44-8676f3705747"
	},
	"signature": "firma-hmac-base64"
}
```

#### 5) NFC capturado

* **Tipo**: `com.idv_suite.api.workflows.nfc_captured.v1`
* **Quando é enviado**: quando há leitura NFC do documento.
* **Para que serve**: enriquecer e contrastar informações documentais.

Tipagem TypeScript:

```ts
type NfcCapturedWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.nfc_captured.v1',
	{
		stepId: string;
		success: boolean;
		data: Record<string, unknown>;
		summary: Record<string, unknown>;
		assets?: string[];
	}
>;
```

Notas:

* Assim como em `id_captured`, `data` e `summary` continuam abertos.

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "cb08900a-8ef2-4f61-95f1-0e39f7686cef",
	"type": "com.idv_suite.api.workflows.nfc_captured.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:01:30.000Z",
	"data": {
		"stepId": "17761cae-0150-43f7-a5b2-a15e7a59f659",
		"success": true,
		"data": {
			"documentNumber": "X1234567"
		},
		"summary": {
			"name": "JUAN",
			"surname": "MARTINEZ"
		}
	},
	"signature": "firma-hmac-base64"
}
```

#### 6) Liveness Passivo avaliado

* **Tipo**: `com.idv_suite.api.workflows.passive_liveness_evaluated.v1`
* **Quando é enviado**: após avaliar o teste de vida passivo.
* **Para que serve**: detectar possíveis tentativas de suplantação.

Tipagem TypeScript:

```ts
type PassiveLivenessEvaluatedWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.passive_liveness_evaluated.v1',
	{
		stepId: string;
		success: boolean;
		diagnostic: string;
		assets?: string[];
	}
>;
```

Notas:

* `diagnostic` não tem enum fechado em código; o exemplo conhecido é `"Live"`.

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "1029c9ed-8945-46e6-9908-9a76ecf402f4",
	"type": "com.idv_suite.api.workflows.passive_liveness_evaluated.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:02:11.000Z",
	"data": {
		"stepId": "95b3f1f9-cf8f-469c-b698-414b3a9656e6",
		"success": true,
		"diagnostic": "Live",
		"assets": ["asset-selfie-id"]
	},
	"signature": "firma-hmac-base64"
}
```

#### 7) Detecção de injeção

* **Tipo**: `com.idv_suite.api.workflows.injection_attack_detected.v1`
* **Quando é enviado**: após avaliar risco de injeção/sintético.
* **Para que serve**: fortalecer controles antifraude em tempo real.

Tipagem TypeScript:

```ts
type InjectionAttackDetectedWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.injection_attack_detected.v1',
	{
		stepId: string;
		success: boolean;
		score: number;
		probability: number;
		status: 'REAL' | 'SPOOF';
		assets?: string[];
	}
>;
```

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "0203b7e9-5ed4-4444-af6d-46cf15f8fc76",
	"type": "com.idv_suite.api.workflows.injection_attack_detected.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:02:30.000Z",
	"data": {
		"stepId": "abf8f66f-0b8d-4fce-a593-eb68d51c210f",
		"success": true,
		"score": 0.12,
		"probability": 0.06,
		"status": "REAL",
		"assets": ["asset-selfie-id"]
	},
	"signature": "firma-hmac-base64"
}
```

#### 8) Matching facial avaliado

* **Tipo**: `com.idv_suite.api.workflows.facial_authentication_evaluated.v1`
* **Quando é enviado**: ao comparar o rosto do documento versus a selfie.
* **Para que serve**: validar correspondência biométrica.

Tipagem TypeScript:

```ts
type FacialAuthenticationEvaluatedWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.facial_authentication_evaluated.v1',
	{
		stepId: string;
		success: boolean;
		authStatus: string;
		similarity: number;
		assets?: string[];
	}
>;
```

Notas:

* `authStatus` não tem enum fechado em código. O exemplo conhecido é `"Positive"`.

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "0087cf6a-77f9-42f7-8f78-8ae94f32bd9a",
	"type": "com.idv_suite.api.workflows.facial_authentication_evaluated.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:02:49.000Z",
	"data": {
		"stepId": "6fe21ca0-2914-49ac-b87c-d3c665304be4",
		"success": true,
		"authStatus": "Positive",
		"similarity": 0.93,
		"assets": ["asset-id-portrait", "asset-selfie"]
	},
	"signature": "firma-hmac-base64"
}
```

#### 9) Cadastro facial

* **Tipo**: `com.idv_suite.api.workflows.facial_enrollment.v1`
* **Quando é enviado**: ao registrar uma identidade biométrica.
* **Para que serve**: habilitar futuras autenticações 1:1.

Tipagem TypeScript:

```ts
type FacialEnrollmentWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.facial_enrollment.v1',
	{
		stepId: string;
		success: boolean;
		authenticationId: string;
		context: 'AUTHENTICATION_ID';
		assetId: string;
	}
>;
```

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "3f2f8df8-424a-4489-95eb-105f7f3e5efb",
	"type": "com.idv_suite.api.workflows.facial_enrollment.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:03:11.000Z",
	"data": {
		"stepId": "9076af18-3de1-4f03-b0f9-ad7deef5d01d",
		"success": true,
		"authenticationId": "auth-123",
		"context": "AUTHENTICATION_ID",
		"assetId": "asset-selfie-id"
	},
	"signature": "firma-hmac-base64"
}
```

#### 10) Verificação facial 1:1

* **Tipo**: `com.idv_suite.api.workflows.facial_verification.v1`
* **Quando é enviado**: ao validar a identidade contra um cadastro anterior.
* **Para que serve**: autenticação ou confirmação de identidade.

Tipagem TypeScript:

```ts
type FacialVerificationWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.facial_verification.v1',
	{
		stepId: string;
		success: boolean;
		authenticationId: string;
		authStatus: string;
		similarity: number;
		assets: string;
	}
>;
```

Notas:

* `authStatus` continua aberto; o exemplo conhecido é `"Positive"`.
* `assets` neste evento é emitido como `string` simples, e não como `string[]`.

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "865fb4e2-65cb-48d9-96a8-157ec95ecef6",
	"type": "com.idv_suite.api.workflows.facial_verification.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:03:33.000Z",
	"data": {
		"stepId": "7242e76b-3271-4f1b-b702-27042512ec66",
		"success": true,
		"authenticationId": "auth-123",
		"authStatus": "Positive",
		"similarity": 0.95,
		"assets": "asset-selfie-id"
	},
	"signature": "firma-hmac-base64"
}
```

#### 11) Matching documental avaliado

* **Tipo**: `com.idv_suite.api.workflows.document_matching_evaluated.v1`
* **Quando é enviado**: ao comparar duas fontes documentais dentro do fluxo.
* **Para que serve**: medir consistência entre documentos ou entre capturas do mesmo titular.

Tipagem TypeScript:

```ts
type DocumentMatchingEvaluatedWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.document_matching_evaluated.v1',
	{
		stepId: string;
		score: number;
		status: 'MATCHED' | 'NOT_MATCHED' | 'WEAK_MATCHED' | 'WEAK_NOT_MATCHED';
		fields: Record<
			string,
			{
				source?: string;
				target?: string;
				similarity: number;
				weight: number;
			}
		>;
	}
>;
```

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "8b3bd547-4fb7-47f0-962d-b7f7d77dbb7d",
	"type": "com.idv_suite.api.workflows.document_matching_evaluated.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:03:55.000Z",
	"data": {
		"stepId": "d537af32-42c1-44b4-aea5-2a8db31dff2f",
		"score": 0.94,
		"status": "MATCHED",
		"fields": {
			"documentNumber": {
				"source": "X1234567",
				"target": "X1234567",
				"similarity": 1,
				"weight": 1
			}
		}
	},
	"signature": "firma-hmac-base64"
}
```

#### 12) Operação finalizada

* **Tipo**: `com.idv_suite.api.workflows.operation_finished.v1`
* **Quando é enviado**: no encerramento da operação (sucesso, rejeição, expiração ou erro).
* **Para que serve**: definir a decisão final e encerrar o processo de negócio.

Tipagem TypeScript:

```ts
type OperationFinishedWebhook = WebhookEnvelope<
	'com.idv_suite.api.workflows.operation_finished.v1',
	{
		status: 'SUCCEEDED' | 'DENIED' | 'ERROR' | 'CANCELLED' | 'BLACKLISTED' | 'EXPIRED';
		reason?: string;
		content: Partial<{
			'passive-liveness': Array<{
				id: string;
				data: {
					success: boolean;
					diagnostic: string;
					assets?: string[];
				};
			}>;
			'injection-attack': Array<{
				id: string;
				data: {
					success: boolean;
					score: number;
					probability: number;
					status: 'REAL' | 'SPOOF';
					assets?: string[];
				};
			}>;
			'face-matching': Array<{
				id: string;
				data: {
					success: boolean;
					authStatus: string;
					similarity: number;
					assets?: string[];
				};
			}>;
			'id-extracted': Array<{
				id: string;
				data: {
					success: boolean;
					data: Record<string, unknown>;
					summary: Record<string, unknown>;
					assets?: string[];
				};
			}>;
			'facial-enrollment': Array<{
				id: string;
				data: {
					success: boolean;
					authenticationId: string;
					context: 'AUTHENTICATION_ID';
					assetId: string;
				};
			}>;
			'facial-verification': Array<{
				id: string;
				data: {
					success: boolean;
					authenticationId: string;
					authStatus: string;
					similarity: number;
					context: 'AUTHENTICATION_ID';
					assets: string;
				};
			}>;
			'document-matching': Array<{
				id: string;
				data: {
					success: boolean;
					score: number;
					status: 'MATCHED' | 'NOT_MATCHED' | 'WEAK_MATCHED' | 'WEAK_NOT_MATCHED';
					fields: Record<string, { source?: string; target?: string; similarity: number; weight: number }>;
				};
			}>;
			'anti-fraud-check': Array<{
				id: string;
				data: {
					success: boolean;
					type: string;
					status: string;
					reason?: string;
					assetId?: string;
					assetHash?: string;
					documentNumber?: string;
					documentType?: string;
					documentCountry?: string;
					deviceId?: string;
					assets?: string[];
				};
			}>;
			'civil-validation': Array<{
				id: string;
				data: {
					serviceResultCode?: number;
					serviceTime?: string;
					serviceResultLog?: string;
					serviceTransactionId?: string;
					serviceFacialAuthenticationResult?: number;
					serviceFacialSimilarityResult?: number;
					civilServiceCountry?: string;
					civilServiceNumber?: string;
					civilServiceData?: Record<string, unknown>;
				};
			}>;
			'document-validation': Array<{
				id: string;
				data: {
					id: string;
					attemptId?: string;
					acceptanceTime?: string;
					decisionTime?: string;
					code?: number;
					vendorData?: string;
					endUserId?: string;
					status?: string;
					reason?: string;
					reasonCode?: number;
					riskScore: number;
					person?: Partial<{
						gender: string;
						idNumber: string;
						lastName: string;
						firstName: string;
						citizenship: string;
						dateOfBirth: string;
						nationality: string;
						yearOfBirth: string;
						placeOfBirth: string;
						pepSanctionMatch: boolean | string;
					}>;
					document?: Partial<{
						type: string;
						state: string;
						number: string;
						country: string;
						validFrom: string;
						validUntil: string;
					}>;
				};
			}>;
		}>;
	}
>;
```

Exemplo:

```json
{
	"specversion": "1.0",
	"id": "63f5f4f7-a4f7-43f7-aa27-f1f49f6fe131",
	"type": "com.idv_suite.api.workflows.operation_finished.v1",
	"source": "/operations/5f2d2e8d-0f0f-4cae-8fc1-55756b6d06f3",
	"time": "2026-03-06T12:04:10.000Z",
	"data": {
		"status": "DENIED",
		"reason": "FACE_MATCHING_FAILED",
		"content": {
			"face-matching": [
				{
					"id": "6fe21ca0-2914-49ac-b87c-d3c665304be4",
					"data": { "success": true, "authStatus": "Positive", "similarity": 0.93 }
				}
			]
		}
	},
	"signature": "firma-hmac-base64"
}
```
