---
title: "POST confirmation.approved"
method: POST
path: "confirmation.approved"
---

# POST confirmation.approved

`POST confirmation.approved` (webhook)

Fired by two sources with two body shapes. A HITL confirmation approved on the approval page sends `ConfirmationWebhookPayload` (`confirmation_id`, the stored `message` — see `encrypted` for what it is — and the signed proof of the decision). A verification request created with `action_type: "confirmation"` that completes sends `WebhookPayload` (`request_id`, `action_context`, `proofs`) to its `callback_url`. Tell them apart by `confirmation_id` vs `request_id`.

## Payload

- union
  - ConfirmationWebhookPayload: Body of `confirmation.approved` / `confirmation.denied`. A timeout is `confirmation.denied` with `response.channel` `system` and `response.text` `timeout`.
    - `event` 'confirmation.approved' | 'confirmation.denied', required: The event type — the same value as the `X-Proof-Event` header
    - `confirmation_id` string, required: MongoDB ObjectId
    - `hitl_id` string, required: HITL configuration the confirmation was sent through
    - `status` 'approved' | 'denied', required
    - `message` string, required: The stored ciphertext envelope, echoed back verbatim. The server never sees the plaintext, so this is never the human sentence — the approver decrypts it in their browser.
    - `response` object, nullable: Approver response details (null while pending)
      - `action` 'approve' | 'deny', required
      - `text` string, nullable: The approver's optional comment, encrypted in the browser by the approval page: a ciphertext envelope (JSON), not plaintext. When `channel` is `system` no approver acted — the confirmation was denied by a timeout, a key rotation or an archived config — and this is that plaintext reason (`timeout`, `key_rotated`, `config_archived`). Null otherwise.
      - `channel` string, nullable
      - `at` string, date-time, required
      - `authorization_id` string, nullable: The enrolled approver's Authorization that resolved this confirmation, verified server-side; null when the decision is not attributable to one approver
    - `proof_token` string: Signed proof of the approver's decision; absent for a system denial (`response.channel` `system`)
    - `encrypted` boolean, required: Whether `message` is a ciphertext envelope — derived from the message, not asserted
    - `resolved_at` string, date-time
  - WebhookPayload: Outbound webhook payload for verification request events (`verification_request.*`), and for `confirmation.approved` when it comes from a verification request created with `action_type: "confirmation"`
    - `event` 'verification_request.completed' | 'verification_request.partial' | 'verification_request.expired' | 'verification.completed' | 'confirmation.approved' | 'confirmation.denied' | 'authorization.approved' | 'authorization.denied' | 'proof.revoked' | 'proof.suspended' | 'proof.unsuspended' | 'account.deletion_scheduled' | 'account.deletion_cancelled', required: Type of webhook event. `proof.suspended`/`proof.unsuspended` fire for a delegation (`ph_dlg_*`) lifecycle act on the account's own configured webhook_url — not a channel to a separately-addressed delegate (h-fix-delegation-suspend-governance).
    - `request_id` string, required: MongoDB ObjectId
    - `reference_id` string
    - `status` 'pending' | 'partial' | 'completed' | 'expired' | 'cancelled', required: Status of a verification request
    - `action_type` 'verification' | '2fa' | 'login' | 'custom' | 'confirmation' | 'authorization', required: Type of action for verification request or authorization
    - `action_context` ActionContext: Context for custom and confirmation action types
      - `title` string: Action title (required for confirmation and custom types)
      - `description` string: Action description with details for the approver
      - `amount` number: Monetary amount for confirmation context
      - `currency` string: Currency code for confirmation context
      - `entity` string: Entity or resource being acted on
    - `client_metadata` object: Client-provided metadata passed through to webhook
    - `completed_at` string, date-time
    - `proofs` WebhookProof[], required
      - `type` 'phone' | 'email' | 'domain' | 'social' | 'wallet' | 'account' | 'telegram_bot', required: Type of asset being verified
      - `identifier` string, nullable, required: The identifier the request named for this asset; null when the recipient supplied it (an identifier-free phone asset)
      - `verified_at` string, date-time
      - `channel` 'whatsapp' | 'telegram' | 'sms' | 'email' | 'dns' | 'http' | 'auto' | 'github' | 'google' | 'facebook' | 'x' | 'linkedin' | 'instagram' | 'youtube' | 'tiktok' | 'ethereum' | 'solana' | 'bitcoin' | 'coinbase' | 'kraken' | 'telegram_bot_token', required: Channel used for verification
      - `proof_token` string: Signed JWT proof token (attestation) — ES256, verified via JWKS
      - `confidence` 'high' | 'standard' | 'degraded': Verification confidence level (present for SMS verifications)
    - `progress` object, required: Verification progress at time of webhook dispatch
      - `completed` integer, required
      - `completed_required` integer, required
      - `required` integer, required
      - `optional` integer, required
      - `total` integer, required
    - `test` boolean: Present and true for test-mode webhooks

## Acknowledgement `200`

Webhook received

---

[API](https://skmtc.dev/proof/apis/proof-holdings-api.md) · [All operations](https://skmtc.dev/proof/apis/proof-holdings-api/llms.txt) · [OpenAPI document](https://skmtc.dev/proof/apis/proof-holdings-api/revisions/b15f506de696?raw)
