> ## 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.

# Age-gate a checkout

> Require an age check before an order of age-restricted goods goes through.

You sell alcohol, tobacco, vapes or other age-restricted goods, and the law requires checking the buyer's age before the sale. This recipe verifies the buyer once, at checkout, and remembers the result on their account.

<Tip>
  On WordPress with WooCommerce, the [plugin](/integration/plugins/wordpress) does all of this without code. On Shopify, see the [Shopify app](/integration/plugins/shopify); on PrestaShop, the [module](/integration/plugins/prestashop).
</Tip>

## The workspace

* **Facial age estimation** for the lightest check: a selfie, with a document only when the estimate cannot confirm the buyer is 18+.
* **Age verification by ID document** when your rules require a document, or a minimum age other than 18 (16 to 25).

## Steps

<Steps>
  <Step title="Check before payment">
    When a buyer reaches checkout with a restricted product, look up whether they are already verified on your side. If they are, continue.
  </Step>

  <Step title="Create a verification">
    On your backend, create a verification with the buyer's ID and the page to return to:

    ```js theme={null}
    const verification = await client.verifications().create({
      external_id: String(user.id),
      callback_url: 'https://shop.example/checkout?verified=1',
      external_metadata: { cart_id: cart.id },
    });
    ```

    Store `verification.id` with the buyer, and return `verification.url` to the page.
  </Step>

  <Step title="Open it">
    Open the `url` with the [Browser SDK](/integration/web/browser-sdk). Keep the payment button disabled.
  </Step>

  <Step title="Record the outcome">
    In your [webhook](/integration/webhooks) handler:

    ```js theme={null}
    export const POST = webhookHandler(async (event) => {
      if (event.status === 'approved') {
        await users.markAgeVerified(event.external_id, event.verification_id);
      }
      if (event.status === 'declined') {
        await users.markAgeCheckFailed(event.external_id, event.reason);
      }
    });
    ```
  </Step>

  <Step title="Let the order through">
    When the buyer is back on the checkout page, read their state from your database. Enable payment only if they are verified; if the webhook has not arrived yet, show "checking" and poll your own backend for a few seconds.
  </Step>
</Steps>

## What to store

* On the user: that they are age-verified, when, and the `verification_id`.
* On each restricted order: the `verification_id` that allowed it, as evidence for an audit.

## Edge cases

* **The buyer closes the widget.** Nothing changes; the next checkout attempt opens the same verification if it is still open, or you create a new one after it is `abandoned` or `expired`.
* **Resubmission requested.** The widget asks the buyer to retake the image in the same verification; wait for the next webhook. See [Handle a resubmission](/recipes/handle-resubmission).
* **Declined as underage.** `verification.age_threshold.failed`: refuse the order. Do not offer an immediate new verification.
* **Guest checkout.** Use the order ID or the email as `external_id`, and verify per order instead of per account.


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