> ## Documentation Index
> Fetch the complete documentation index at: https://docs.proofage.net/llms.txt
> Use this file to discover all available pages before exploring further.

# October 2026

> What changed in ProofAge in October 2026.

<Update label="October 9" description="Domains">
  * **ProofAge moved to proofage.net.** The API is now `https://api.proofage.net/v1`, the console `https://app.proofage.net`, these docs `https://docs.proofage.net`, and new verification links are issued on `idv.proofage.net`. The API spec, the Postman collection and the examples on these pages name the new hosts.
  * **Nothing you have breaks.** `api.proofage.xyz` keeps answering, with the same keys and the same signatures, so an integration or an SDK release that names it needs no change. Links issued before the move keep working on the host they were issued on, the browser SDK loader is still served from `app.proofage.xyz`, and MCP clients connected to `app.proofage.xyz/mcp/admin` stay connected. Console pages opened on the old host are sent to the new one.
  * If your site has a Content Security Policy that names `idv.proofage.xyz` in `frame-src`, add `idv.proofage.net`: the verification URL the API returns is now on that host.
  * The server SDKs still default to `api.proofage.xyz`; their next releases switch to `api.proofage.net`.
</Update>

<Update label="October 9" description="Plugins">
  * **PrestaShop module.** Age verification for PrestaShop 8.1 to 9.x, without code: protect products, categories, CMS pages, controllers, URL paths or the whole shop, with add-to-cart, checkout and order creation blocked server-side until the shopper is verified. Download it from [GitHub](https://github.com/ProofAge/prestashop-plugin). See [PrestaShop](/integration/plugins/prestashop).
</Update>

<Update label="October 8" description="API, webhooks and Zapier">
  * **List verifications.** `GET /v1/verifications` lists the workspace's verifications, newest first, as `{"data": [...], "next_cursor": ...}`. Filter by `status` (comma-separated) and `external_id`; page with `limit` (1 to 100, 20 by default) and `cursor`. Use it to find a verification by your own ID. The query string is part of the signature, sorted by name, with a comma written as `%2C`. See [Reading results](/integration/retrieving-results#finding-a-verification-by-your-id).
  * **Webhook subscriptions.** `POST`, `GET` and `DELETE /v1/webhook-subscriptions` subscribe more URLs to the decision webhooks, in addition to the workspace's webhook URL, and remove them: up to 50 per workspace (`422 WEBHOOK_SUBSCRIPTION_LIMIT` past that), optionally for some statuses only. A subscription gets `status.updated` webhooks only, never `data.updated`. Unless it was created with `include_document_data: true`, its body leaves out `document`, `fingerprint_signals` and `manual_moderation.performed_by`. Its deliveries are signed with the secret key that created it, or with the active key once that key is deleted. A `410` answer deletes the subscription. See [Webhooks](/integration/webhooks#webhook-subscriptions).
  * Because a subscription's body can leave it out, `document` is no longer required in the webhook schema. The workspace's webhook URL still receives it on every webhook.
  * **Set a test outcome from your code.** `POST /v1/verifications/{id}/test-outcome` sets `approved`, `declined`, `review` or `resubmission_requested` on a test workspace's verification that is not final, with an optional `reason` note. One nobody has opened is moved through `started` and `submitted` first, so exactly one decision webhook is sent. A live workspace answers `403 TEST_WORKSPACE_ONLY`; a final verification `422 INVALID_STATUS`. See [Sandbox and test data](/integration/sandbox-testing#set-the-outcome-from-your-code).
  * **Webhook URLs must be public.** A new webhook URL, on a workspace or a subscription, must be `https`; private, loopback, link-local and cloud-metadata addresses, local names and URLs with credentials are refused, and a Unicode host name must be written in punycode. The address is checked again before each delivery, and a delivery to one that has become private is skipped. An `http` URL saved before keeps working. See [The webhook URL](/integration/webhooks#the-webhook-url).
  * **Redirects are no longer followed.** A `3xx` from your webhook URL is recorded as the answer and not retried; set the URL your handler serves. Only the first 2 KB of your response body are kept with each attempt.
  * `callback_url` and the workspace's redirect URL accept `http` and `https` URLs only.
  * **The per-IP rate limit counts each workspace separately**, so workspaces calling from one IP address no longer share it. The per-workspace limit is unchanged. See [Rate limiting](/integration/rate-limiting).
  * **ProofAge for Zapier.** Send verification links from any Zap and act on the decision, without code. Shared by invite for now. See [Zapier](/integration/zapier).
  * SDK releases to match: `@proofage/node` 0.13.0, `proofage` (Python) 0.8.0, `proofage/php-sdk` 0.8.0 and `proofage/laravel-client` 0.10.0 add listing verifications, webhook subscriptions and test outcomes. In the SDK types `manual_moderation.performed_by` is now optional, because subscription deliveries leave it out.
</Update>

<Update label="October 8" description="Webhooks and console">
  * **Correct document fields the reader got wrong.** An administrator or a support specialist can correct a field on an approved, declined or in-review verification, in the console or with the new MCP tool `correct-document-fields`. The status does not change and no check runs again. The recognised value is kept; your corrected value is what the webhook and `GET /v1/verifications/{id}/document` return. See [Verifications and review](/console/verifications-and-review#correcting-document-fields).
  * **New webhook event `data.updated`.** Every webhook payload now has `event`: `status.updated` for the status webhooks you already receive, and `data.updated` when a correction is made. A `data.updated` has the same fields as a status webhook, with the current `status`, the corrected `document` and a new `changed_fields` (the names of the fields changed, no values). A payload without `event` is a `status.updated`, so `event` is optional in the schema. **Read `event` before `status`**: a handler that ignores it sees what looks like a repeat of the status it already has, and must not treat it as a new decision. See [Webhooks](/integration/webhooks#data-corrections).
  * The document type, issuing country and issuing state or province can be corrected too, on every workspace. A corrected country clears the state or province that was read with the old one, unless that is corrected as well. `changed_fields` then names `type`, `issuing_country` or `issuing_subdivision`.
  * Resending or retrying an older webhook sends the document as it is now, corrections included.
  * SDK releases to match: `@proofage/node` 0.12.0, `proofage` (Python) 0.7.0, `proofage/php-sdk` 0.7.0 and `proofage/laravel-client` 0.9.7. The new keys are optional in their types.
</Update>

<Update label="October 8" description="Dashboard and MCP">
  * **Resubmission insights count people, not verifications.** The dashboard card and the `resubmission` part of the MCP `get-verification-stats` now merge a person's verifications by `external_id` within a workspace, and count each person once, on the day of their final outcome. Someone declined on one link and approved on the next is one approval on the 2nd attempt, on the day of the approval, and no longer a decline plus a first-attempt approval. A new verification after an approval starts a new count of its own, and verifications without an `external_id` are counted one by one. The response keys are unchanged, `volume` stays per verification, and the MCP `legend` has a new `person` entry.
</Update>

<Update label="October 3" description="API">
  * **`DOCUMENT_PORTRAIT_NOT_FOUND` on a document front upload.** A front image with no photo of the holder on it, such as a closed passport cover or the back of a card, is now refused at upload with `422` and this code, instead of failing later in the verification. Ask the person to retake the document photo. Not sent for every workspace; handle it like the other upload codes in [Error handling](/integration/error-handling).
</Update>

<Update label="October 2" description="Billing">
  * **The 500 free verifications no longer expire after 15 days.** They are used before your card is charged, and there is no monthly minimum while any are left or in a month that used them. Your administrators get an email when 100, 30 and 0 are left. See [Pricing](/getting-started/pricing#trial).
</Update>

<Update label="October 2" description="API">
  * **`issuing_subdivision` in the document result.** `issuing_subdivision` in `document` is the state or province that issued the document, as a bare code right after `issuing_country` (for example `FL` with `US`), or `null`. Today it is filled for US driving licences and ID cards. It is on every workspace, in `GET /v1/verifications/{id}/document`, in the webhook `document` and in the MCP `list-verifications` document, and it is kept after erasure. See [Reading results](/integration/retrieving-results).
  * SDK releases to match: `@proofage/node` 0.11.0, `proofage` (Python) 0.6.0, `proofage/php-sdk` 0.6.0 and `proofage/laravel-client` 0.9.6.
</Update>

<Update label="October 1" description="API">
  * **`address` in the document result, on identity (KYC) workspaces only.** `GET /v1/verifications/{id}/document` now returns `address` in `fields`: the printed text as read, trimmed, `null` when empty, not parsed and not normalised. It may contain line breaks. `fields` carries eleven keys on KYC workspaces, seven of them KYC-only. Age workspaces do not receive `address`.
  * **The decision webhook carries `document`.** Every decision webhook now has the same `document` object as `GET /v1/verifications/{id}/document`, without the images: `type`, `issuing_country` and `fields`, with eleven fields on identity (KYC) workspaces and four on age workspaces. It is present on every status and is all `null` when nothing was read, and on test workspaces. A resend or a manual retry carries the document as it is now; an automatic retry carries the body as first sent. Stored webhook bodies in your logs now hold the person's name and birth date until erasure. If your handler validates the body with a strict schema, allow the new key. See [Webhooks](/integration/webhooks#payload).
  * **`nationality`** is published only when the document itself states it.
  * **MCP `list-webhook-deliveries`** returns the last attempt's `request_body` only with `include_payload: true`, like `payload`.
  * **Erasure covers every stored copy of a webhook body.** The copy of the body kept with each delivery attempt, and what a receiver answered, are now erased with the rest of the person's data, and an erasure that lands while a delivery is in flight is no longer undone.
  * **`gender` can be `X`.** `F`, `M` or `X`, where `X` means the document states that the sex is unspecified.
  * SDK releases to match: `@proofage/node` 0.10.0, `proofage` (Python) 0.5.0, `proofage/php-sdk` 0.5.0 and `proofage/laravel-client` 0.9.5. They type `address` and the webhook `document`, both optional.
    See [Reading results](/integration/retrieving-results) and [Data models](/api-reference/data-models#document-result).
</Update>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.