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

# Integrate gVault webhooks

> Receive and verify gVault access, fee-wrapper, and fee-configuration lifecycle events.

Use gVault webhooks to react to asynchronous API and onchain state changes
without polling every resource. Webhooks complement authoritative API and
onchain reads; they do not replace them.

## 1. Create a registration

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://sandbox.groundtech.co/v2/gvaults/webhooks \
  -H "Authorization: Bearer $GROUND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://ops.acme.com/webhooks/ground-gvaults",
    "events": [
      "gvault.allowlist.status_changed",
      "fee_wrapper.status_changed",
      "fee_wrapper.fees.status_changed",
      "fee_wrapper.allowlist.status_changed"
    ],
    "description": "Sandbox gVault lifecycle events"
  }'
```

Store the returned `id` and `secret`. The secret is shown only in the create
response and is not returned by list or get calls.

## 2. Verify every delivery

Ground sends JSON with `Ground-Event-Id`, `Ground-Event-Type`, and
`Ground-Signature` headers. Verify the signature against the exact raw request
body before parsing JSON:

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const crypto = require('crypto');

function verifyGroundSignature(rawBody, header, secret, tolerance = 300) {
  const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')));
  const timestamp = Number(parts.t);
  if (!Number.isFinite(timestamp)) throw new Error('Invalid signature timestamp');
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > tolerance) {
    throw new Error('Signature timestamp outside tolerance');
  }

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');
  const actual = parts.v1 || '';
  if (expected.length !== actual.length ||
      !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(actual))) {
    throw new Error('Invalid signature');
  }
}
```

Return a `2xx` response promptly, then process the event asynchronously.
Deduplicate deliveries by `Ground-Event-Id`.

## 3. Reconcile the resource

Route on `type` or `Ground-Event-Type`, then use the identifiers in `data`
to read the current resource state. Events can be delayed or delivered more
than once, so update local state monotonically and never assume arrival order.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "73a8e0c9-c20d-4b10-b9a4-0f0ff6b5cb9c",
  "type": "fee_wrapper.status_changed",
  "createdAt": "2026-09-03T17:35:39.000Z",
  "data": {
    "feeWrapperId": "8f4c52d7-62ab-4db9-a964-92f481f6b851",
    "status": "ready",
    "contractAddress": "0x7C4A1cb5a3D5C3c4E019c3f6aB29A0f98C21B814"
  }
}
```

## 4. Inspect delivery history

List events across the organization with
`GET /v2/gvaults/webhooks/events`, or for one registration with
`GET /v2/gvaults/webhooks/{id}/events`. Use `status`, `attemptCount`,
`deliveredAt`, and `lastError` to reconcile your receiver.

## 5. Delete a registration

Call `DELETE /v2/gvaults/webhooks/{id}`. Deletion stops future
deliveries; retain your processed-event ledger for audit and deduplication.

API reference:
[create](/api-reference/create-gvault-webhook),
[list registrations](/api-reference/list-gvault-webhooks),
[get](/api-reference/get-gvault-webhook),
[list events](/api-reference/list-gvault-webhook-events), and
[delete](/api-reference/delete-gvault-webhook).
