> For the complete documentation index, see [llms.txt](https://developers.share.inc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.share.inc/webhooks/setup.md).

# Setup

This guide walks you from "we have credentials" to "Share is POSTing events to my server, signed, and I can verify them." You should be done in about ten minutes.

By the end you will have:

1. Registered a webhook subscription against `POST /v1/webhooks`
2. Captured the `signingSecret` returned on creation
3. Received your first delivery and confirmed the HMAC matched
4. Listed, updated, and deactivated the subscription

There is no partner console for webhook setup — it is a normal Share API call, authenticated with the same `X-Access-Id` / `X-Access-Secret` pair you use for every other endpoint.

## What you need

| Item                     | How to get it                                                              |
| ------------------------ | -------------------------------------------------------------------------- |
| `X-Access-Id`            | Provisioned by Share with your partner account                             |
| `X-Access-Secret`        | Same — treat it as a password                                              |
| A public HTTPS URL       | Your endpoint that accepts `POST` and returns 2xx quickly                  |
| A place to store secrets | The `signingSecret` is only returned in full once — capture it on creation |

For local development, point Share at an `https://` tunnel (`ngrok`, `cloudflared`, your platform's preview URL). Share will not deliver to `http://` or to an IP literal.

### Delivery URL requirements

The `url` you register must satisfy both a registration check and a send-time check:

* **`https` only.** `POST /v1/webhooks` and `PUT /v1/webhooks/:id` reject any non-`https` URL with a `400`.
* **No internal targets, verified at delivery time.** Before every delivery attempt — and every retry — Share resolves the target host and refuses to connect if it resolves to a loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), private (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fc00::/7`), or cloud-metadata (`169.254.169.254`) address. The check runs at send time, not just at registration, so a hostname that later resolves to an internal address is still refused. A delivery blocked this way is marked `failed` and is **not** retried.

## Step 1 — Register the subscription

The Share API endpoint is `POST /v1/webhooks`. The minimum payload is a `url` and an `events` array:

```bash
curl -sS https://api.share.inc/v1/webhooks \
  -X POST \
  -H "X-Access-Id: <your-access-id>" \
  -H "X-Access-Secret: <your-access-secret>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "https://api.partner.com/webhooks/share",
    "events": [
      "payment.received",
      "billing.overdue",
      "subscriber.suspended"
    ],
    "description": "Production billing notifications"
  }'
```

A successful response looks like:

```json
{
  "success": true,
  "data": {
    "id": "whk_01HXYZ...",
    "url": "https://api.partner.com/webhooks/share",
    "events": ["payment.received", "billing.overdue", "subscriber.suspended"],
    "headers": [],
    "isActive": true,
    "signingSecret": "whsec_3f9c…b21a",
    "apiVersion": "2026-05-22",
    "createdAt": "2026-05-26T09:14:22.000Z",
    "updatedAt": "2026-05-26T09:14:22.000Z"
  }
}
```

**Capture `signingSecret` now.** Subsequent reads return it masked (`whsec_****...****`). If you lose it, your only option is to issue a new one — see [Step 6](#step-6-rotate-or-disable).

### Decisions you make on creation

| Field           | Required | Notes                                                                                                                                                          |
| --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`           | yes      | `https://` only. Share retries non-2xx and timeouts, so make the handler fast — ack first, process async                                                       |
| `events`        | yes      | At least one. See [Event catalog](/webhooks/event-catalog.md) for the full list                                                                                |
| `signingSecret` | no       | Omit and Share generates one. Supply your own if you want to mint it from your secrets manager — must be ≥ 32 random bytes                                     |
| `description`   | no       | Free-form label that shows up in `GET /v1/webhooks` — useful when you have several                                                                             |
| `headers`       | no       | Custom headers Share sends on every delivery (for example a static bearer token your edge expects). They ride alongside the auto-generated `X-Share-*` headers |
| `apiVersion`    | no       | Pins the envelope version this subscription receives. Defaults to the latest stable — see [Envelope](/webhooks/envelope.md)                                    |

Custom `headers` cannot override the `X-Share-*` delivery headers (`X-Share-Signature`, `X-Share-Timestamp`, `X-Share-Event-Id`, `X-Share-Event-Type`, `X-Share-Delivery-Id`, `X-Share-Delivery-Attempt`).

The full operation reference (every field, every response code) is embedded below directly from the OpenAPI spec:

## Step 2 — Stand up the receiver

The receiver must:

* Accept `POST` at the URL you registered
* Return a `2xx` status as quickly as possible (within a few seconds — slow handlers count as failures and trigger retries)
* Read the raw request body **before** any JSON parser touches it, so you can compute the HMAC over the exact bytes Share signed

A minimal Express handler:

```javascript
import express from 'express';
import crypto from 'crypto';

const app = express();

// Important: capture the raw body for signature verification.
app.post(
  '/webhooks/share',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const timestamp = req.header('X-Share-Timestamp');
    const provided = (req.header('X-Share-Signature') ?? '').replace(
      /^sha256=/,
      '',
    );
    const eventType = req.header('X-Share-Event-Type');
    const body = req.body; // Buffer, raw

    const expected = crypto
      .createHmac('sha256', process.env.SHARE_SIGNING_SECRET)
      .update(`${timestamp}.${body.toString('utf8')}`)
      .digest('hex');

    const a = Buffer.from(expected, 'hex');
    const b = Buffer.from(provided, 'hex');
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).end();
    }

    // Acknowledge first, process async.
    res.status(204).end();
    enqueue({ eventType, payload: JSON.parse(body.toString('utf8')) });
  },
);
```

The signature is computed over the string `<timestamp>.<raw-body>`, not over the body alone — including the timestamp prevents replay of an old delivery with a stolen body. See [Signature verification](/webhooks/verification.md) for examples in other languages.

## Step 3 — Trigger a test delivery

The easiest event to provoke is `subscriber.updated`: edit a sandbox subscriber and Share fires the event.

```bash
curl -sS https://api.share.inc/v1/subscribers/sub_01HXYZ... \
  -X PATCH \
  -H "X-Access-Id: <your-access-id>" \
  -H "X-Access-Secret: <your-access-secret>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "metadata": { "webhookSmokeTest": "ok" } }'
```

Within a few seconds you should see a `POST` land at your URL with these headers set:

| Header                     | Meaning                                                              |
| -------------------------- | -------------------------------------------------------------------- |
| `X-Share-Event-Type`       | The event name, e.g. `subscriber.updated`                            |
| `X-Share-Event-Id`         | Stable across retries — use it to deduplicate                        |
| `X-Share-Delivery-Id`      | Changes on every attempt                                             |
| `X-Share-Delivery-Attempt` | Attempt number (1 on first try)                                      |
| `X-Share-Timestamp`        | Unix seconds, included in the signed string                          |
| `X-Share-Signature`        | `sha256=<hex(HMAC-SHA256(signingSecret, "<timestamp>.<raw-body>"))>` |

Confirm by re-reading the subscription:

```bash
curl -sS https://api.share.inc/v1/webhooks/whk_01HXYZ... \
  -H "X-Access-Id: <your-access-id>" \
  -H "X-Access-Secret: <your-access-secret>"
```

`lastTriggeredAt` will have advanced.

## Step 4 — Handle retries idempotently

Share retries any delivery that:

* Times out (no response within the delivery deadline)
* Returns a non-2xx response
* Returns a 2xx but the connection drops before the body is fully received

Retries use exponential backoff and reuse the same `X-Share-Event-Id`. Persist `event-id`s you've already handled and short-circuit duplicates — your end users see the side-effect once, not three times. This is the same posture as the [`Idempotency-Key`](/getting-started/idempotency.md) contract on `POST` / `PATCH` calls in the other direction.

If you genuinely cannot recover (signature mismatch, validation failure on your side), respond `4xx`. Share marks the delivery failed and stops retrying after the configured ceiling — visible to your team via the forthcoming `GET /v1/webhooks/:id/deliveries` debug endpoint.

## Step 5 — Manage your subscriptions

List everything you have registered:

```bash
curl -sS https://api.share.inc/v1/webhooks \
  -H "X-Access-Id: <your-access-id>" \
  -H "X-Access-Secret: <your-access-secret>"
```

Update events, headers, URL, or `isActive` in place:

```bash
curl -sS https://api.share.inc/v1/webhooks/whk_01HXYZ... \
  -X PUT \
  -H "X-Access-Id: <your-access-id>" \
  -H "X-Access-Secret: <your-access-secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["payment.received", "payment.failed"],
    "isActive": true
  }'
```

The `events` array **replaces** the existing list — to add an event, send the full set you want.

Delete permanently:

```bash
curl -sS https://api.share.inc/v1/webhooks/whk_01HXYZ... \
  -X DELETE \
  -H "X-Access-Id: <your-access-id>" \
  -H "X-Access-Secret: <your-access-secret>"
```

To pause deliveries without losing the subscription, send `PUT` with `{ "isActive": false }` instead.

## Step 6 — Rotate or disable

To rotate the signing secret, send `PUT` with a new `signingSecret`. The old secret stops working **immediately** — there is no overlap window, so deploy the new secret to your receiver first, then rotate.

To temporarily stop deliveries while you debug your receiver, set `isActive: false`. Events that fire while paused are **not** queued for later replay — they are dropped on the floor relative to this subscription. If you need historical state, reconcile from the REST API.

## Common pitfalls

* **Parsing the body before computing the HMAC.** JSON parsers normalize whitespace and field ordering — the signature will not match. Read the raw bytes first.
* **Logging the `signingSecret`.** Treat it like `X-Access-Secret`. Redact it from logs and exception trackers.
* **Returning 2xx before you've persisted the event.** If your worker crashes after the ack, the event is gone. Either persist before acking, or make your downstream handler idempotent on `X-Share-Event-Id`.
* **Slow handlers.** A 30-second receiver looks identical to a dead one; Share will retry. Ack fast (`204 No Content`), do the work on a queue.
* **Re-using URLs across environments.** If your staging endpoint receives a production event, you will move real money. Register separate subscriptions per environment.

## Limits today

* Self-service setup covers **webhook subscriptions only**. BSS/OSS integrations (Splynx and the rest) are still onboarded by Share — ask your account manager.
* `events[]` only accepts names from the [Event catalog](/webhooks/event-catalog.md). Sending an unknown name returns a 400 with the offending entry in `rejectionReasons`.
* One subscription per `url` per provider. To send the same events to two URLs, register two subscriptions.

## Next steps

* [Signature verification](/webhooks/verification.md) — language-specific examples and the constant-time comparison rule
* [Envelope](/webhooks/envelope.md) — the `id` / `type` / `data` shape every event carries
* [Event catalog](/webhooks/event-catalog.md) — every event you can subscribe to and the payload each one delivers
