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

# Gate webhook

> Implement the Foil Gate webhook to provision accounts when developers approve a CLI signup, return encrypted credentials, and handle retries idempotently.

When a developer approves signup, Gate posts a `gate.session.approved` event to your webhook endpoint. Unlike other Foil webhooks, this one has a **request/response contract**: your handler must return an encrypted credentials envelope that the CLI decrypts and writes into the developer's project.

This page covers the Gate-specific bits. For everything common to all Foil webhooks — signature verification, retries, headers, the delivery log — see [Webhooks](/webhooks).

## Set up the endpoint

Create a webhook endpoint subscribed to `gate.session.approved`, then point your Gate service at it via `webhook_endpoint_id`. See [Webhooks → Endpoints](/webhooks#endpoints) for both the dashboard flow and the API.

## Verify the signature

Standard Foil HMAC verification — see [Webhooks → Authentication](/webhooks#authentication) for ready-to-paste code in Node.js, Python, Go, Ruby, and PHP.

## Handle the payload

The envelope is the standard [webhook envelope](/webhooks#envelope). The `gate.session.approved` `data` object is:

```json theme={"dark"}
{
  "service_id": "foil",
  "gate_session_id": "gate_abc123...",
  "gate_account_id": "gacct_abc123...",
  "account_name": "my-project",
  "metadata": null,
  "delivery": {
    "version": 1,
    "algorithm": "x25519-hkdf-sha256/aes-256-gcm",
    "key_id": "<base64url sha256 of public_key>",
    "public_key": "<base64url raw X25519 public key>"
  },
  "foil": {
    "verdict": "human",
    "score": 0.12
  }
}
```

| Field             | Description                                                                              |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `service_id`      | The registry service being provisioned. Use it if one webhook handles multiple services. |
| `gate_session_id` | Idempotency key. Use this to avoid creating duplicate accounts on retries.               |
| `gate_account_id` | Stable identifier for this signup. Store it alongside your internal account.             |
| `account_name`    | The name the developer chose (defaults to their project directory name).                 |
| `metadata`        | Optional key-value pairs the developer passed during signup.                             |
| `delivery`        | CLI-provided encrypted delivery metadata. Encrypt your response outputs to this key.     |
| `foil.verdict`    | `human` or `inconclusive`. Bot verdicts are blocked before reaching your webhook.        |

The full schema is also published in the [API reference](/api-reference/introduction) under **Webhooks**.

## Return credentials

Your webhook must return an encrypted delivery envelope, not raw keys or onboarding links. Gate owns the service metadata like `docs_url`; your webhook should only return the encrypted customer outputs.

```json theme={"dark"}
{
  "encrypted_delivery": {
    "version": 1,
    "algorithm": "x25519-hkdf-sha256/aes-256-gcm",
    "key_id": "<same key_id>",
    "ephemeral_public_key": "<base64url raw X25519 public key>",
    "salt": "<base64url 32 bytes>",
    "iv": "<base64url 12 bytes>",
    "ciphertext": "<base64url>",
    "tag": "<base64url 16 bytes>"
  }
}
```

When decrypted by the CLI, the ciphertext should contain your registry-owned `env_vars` only, for example:

```json theme={"dark"}
{
  "version": 1,
  "outputs": {
    "YOURPRODUCT_PUBLISHABLE_KEY": "pk_live_...",
    "YOURPRODUCT_SECRET_KEY": "sk_live_..."
  }
}
```

## Idempotency

Gate may retry your webhook. Use `gate_session_id` to make requests idempotent:

```javascript theme={"dark"}
// Check if we already processed this session
const existing = await db.findByGateSessionId(gate_session_id);
if (existing) return res.json(existing.credentials);

// First time — create the account
const account = await createAccount(account_name);
// ... save gate_session_id for future retries
```

The general retry rules — 5 attempts, ≥ 1 minute between attempts, dedupe by envelope `id` — apply here too. See [Webhooks → Retries](/webhooks#retries).

## What's next

* [Agent tokens](/gate/agent-tokens) — verify the tokens Gate issues
* [Dashboard login](/gate/dashboard-login) — let developers access your dashboard
