# Webhook

Webhooks let your systems receive real-time notifications when something happens in The Wallet Crew. You register an HTTPS endpoint, select events, and The Wallet Crew sends a `POST` request each time one of those events occurs.

This is the simplest way to keep your CRM, analytics, or operational systems in sync without polling APIs.

<details>

<summary><strong>Real-world examples</strong></summary>

* Send a “pass installed” event to your CRM to measure adoption per campaign.
* Trigger a customer welcome journey when a customer profile is upserted.
* Log pass lifecycle events (`Pass:Created`, `Pass:Updated`) to your data warehouse.
* Track in-store QR usage by listening to redirect events (`Redirect:Redirected`).

</details>

{% hint style="info" %}
If you also enforce network restrictions, you can allowlist The Wallet Crew outgoing IPs. Do not rely on IPs only. Always validate `x-neostore-signature`.

See [Infrastructure](https://docs.thewalletcrew.io/develop/architecture/infrastructure).
{% endhint %}

## Create and manage a webhook

You manage webhooks from the API endpoint `POST /api/{tenantId}/webhooks`. A webhook defines three things: where to send requests, which events to send, and whether the webhook is enabled.

### Create a webhook (API)

When creating a webhook, send:

* `endpoint`: the HTTPS URL that will receive `POST` requests.
* `events`: the events you want to subscribe to.
* `enabled`: whether delivery is active.

You can subscribe to multiple events in one webhook. You can also use `*` to subscribe to all sub-events under a category.

Example: subscribe to all customer events and to `Pass:Created`.

```json
{
  "events": ["Customer:*", "Pass:Created"]
}
```

{% hint style="warning" %}
Treat `signatureSecret` like a password. Store it securely. Use it only server-side.
{% endhint %}

```bash
curl -X 'POST' \
  'https://app.neostore.cloud/api/{tenantId}/webhooks' \
  -H 'X-API-KEY: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "description": "Sync The Wallet Crew events to our CRM",
  "events": [
    "Customer:*",
    "Pass:Created"
  ],
  "endpoint": "https://your-endpoint-url.com/webhook",
  "enabled": true
}'
```

If creation succeeds, the API returns the webhook object, including `id` and `signatureSecret`.

```json
{
  "id": "UniqueWebhookID",
  "signatureSecret": "GeneratedSignatureSecret",
  "description": "Sync The Wallet Crew events to our CRM",
  "events": ["Customer:*", "Pass:Created"],
  "endpoint": "https://your-endpoint-url.com/webhook",
  "enabled": true
}
```

### Update, list, and delete

You can manage webhooks using `GET`, `PATCH`, and `DELETE` on the same resource.

For the full API spec, see the [API reference](https://docs.thewalletcrew.io/develop/api-reference).

<div data-with-frame="true"><figure><img src="https://3566051324-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWLc8AHXW4tdrAXUBfrYF%2Fuploads%2Fgit-blob-fe3004196dd6482c17306a982a89f8ee2caba07d%2Fwebhook_2.png?alt=media" alt="Webhook configuration screen showing event subscriptions and endpoint URL."><figcaption><p>Configure which events are delivered to which endpoint.</p></figcaption></figure></div>

## What The Wallet Crew sends (headers and payload)

Each webhook delivery is an HTTP `POST` request with headers and a JSON body. The body depends on the event type. Every payload includes the event metadata fields prefixed with `__`.

### HTTP headers

* `x-neostore-signature`: HMAC SHA-256 signature of the request body, generated using your `signatureSecret`.
* `x-neostore-eventname`: event name that triggered the webhook (example: `Customer:Upserted`).
* `x-neostore-tenantid`: tenant identifier in The Wallet Crew.

### Example payload

```json
{
  "passId": "ExamplePassID",
  "passType": "user",
  "identifiers": {
    "shopifyId": "ExampleShopifyID"
  },
  "metadata": null,
  "__id": "UniqueEventID",
  "__creationDate": "2025-02-17T12:27:31.6316643Z",
  "__eventName": "Pass:Created"
}
```

### How to process events reliably

Use `__id` as an idempotency key. If your endpoint receives the same payload twice, you can safely ignore the duplicate.

Keep your handler fast. A common pattern is to validate the signature, enqueue the event internally, then return `2xx`.

## Verify webhook authenticity (signature)

Validate every webhook request using `x-neostore-signature`. This ensures the request body was sent by The Wallet Crew and was not modified in transit.

To validate it, compute an HMAC SHA-256 of the raw request body using your `signatureSecret`, then compare it with the header value.

{% hint style="warning" %}
Signature validation must use the exact raw body bytes you received. Do not re-serialize JSON before hashing.
{% endhint %}

## Common events

The event list evolves. Use the API reference as the source of truth for event names and payload shapes: [API reference](https://docs.thewalletcrew.io/develop/api-reference).

Below are the most common events teams integrate with.

<details>

<summary><strong>Customer events</strong></summary>

`Customer:Upserted` is sent when a customer is created or updated.

```json
{
  "__id": "string",
  "__creationDate": "YYYY-MM-DDTHH:mm:ssZ",
  "__eventName": "Customer:Upserted",
  "flowName": "string",
  "identifiers": {},
  "attributes": {}
}
```

</details>

<details>

<summary><strong>Pass lifecycle events</strong></summary>

Typical pass lifecycle events:

* `Pass:Created`
* `Pass:Installed`
* `Pass:Uninstalled`
* `Pass:Updated`
* `Pass:UpdateSent`

Example: `Pass:Installed` includes device fields.

```json
{
  "__id": "string",
  "__creationDate": "YYYY-MM-DDTHH:mm:ssZ",
  "__eventName": "Pass:Installed",
  "passId": "string",
  "passType": "string",
  "identifiers": {},
  "metadata": {},
  "device": "string",
  "deviceIdentifier": "string"
}
```

</details>

<details>

<summary><strong>Redirect events</strong></summary>

`Redirect:Redirected` is sent when a user opens a minified URL (redirect).

```json
{
  "__id": "string",
  "__creationDate": "YYYY-MM-DDTHH:mm:ssZ",
  "__eventName": "Redirect:Redirected",
  "redirectId": "string",
  "newUri": "string"
}
```

</details>

## FAQ

<details>

<summary><strong>Can I subscribe to all events?</strong></summary>

Yes. Use the `*` wildcard in the `events` array, for example `Customer:*`, to subscribe to all customer sub-events.

Use this carefully. You may receive more events than you need.

</details>

<details>

<summary><strong>Should my endpoint be public?</strong></summary>

Yes. The endpoint must be reachable from The Wallet Crew over HTTPS.

If you restrict inbound traffic, allowlist The Wallet Crew outgoing IPs and still validate the signature.

</details>

<details>

<summary><strong>Where do I find the exact payload schema for an event?</strong></summary>

Use the API reference. It is the source of truth for event names and payload shapes.

Start here: [API reference](https://docs.thewalletcrew.io/develop/api-reference).

</details>
