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

# Webhooks

> Get a signed POST the moment something happens in StockShift, instead of polling for it.

A webhook endpoint is an address StockShift sends events to. Add one in the app under **Settings > Webhooks**, or with the [webhook endpoint operations](https://docs.stockshift.io/api-reference/webhooks/list-webhook-endpoints.md) in the API.

## Add an endpoint

1. Press **New endpoint**.
2. Enter the URL. It must be HTTPS and reachable on the internet. Private and local addresses are refused.
3. Tick the events you want. The [event list](https://docs.stockshift.io/api-reference/webhook-events.md) has the body of each one.
4. Press **Add endpoint**.
5. Copy the signing secret it shows. It is shown once. **Rotate secret** makes a new one.

**Send test ping** sends a `webhook.ping` event right away so you can check your endpoint.

## What your endpoint receives

Each event is a `POST` with a JSON body:

```json theme={null}
{
  "id": "0199f2d4-6b1e-7c3a-9a51-2f0e8c1d4b7a",
  "type": "inventory.stock.adjusted",
  "created": "2026-10-29T14:03:11Z",
  "data": {}
}
```

Answer with any `2xx` within 10 seconds. StockShift does not follow redirects.

An event can arrive more than once and out of order. Retries keep the same `id`, so store it and skip events you have already handled.

## Check the signature

Check the `StockShift-Signature` header before you trust the body. It looks like `t=1760000000,v1=5257a8...`:

* `t` is the Unix time the event was sent.
* `v1` is the HMAC-SHA256 of `<t>.<raw body>` under the endpoint's signing secret, in hex.

Compute the same HMAC over the raw body, exactly as it arrived, compare it in constant time and reject anything with a `t` more than five minutes old. Every SDK has a helper that does all of this:

```ts TypeScript theme={null}
import { verifyWebhook, WebhookVerificationError } from "@stockshift/sdk";

export async function POST(request: Request): Promise<Response> {
  try {
    const event = await verifyWebhook({
      payload: await request.text(),
      header: request.headers.get("StockShift-Signature"),
      secret: process.env.STOCKSHIFT_WEBHOOK_SECRET!,
    });
    if (event.type === "inventory.stock.adjusted") {
      console.log(event.data.item_id, event.data.quantity);
    }
    return new Response(null, { status: 204 });
  } catch (error) {
    if (error instanceof WebhookVerificationError) {
      return new Response(null, { status: 400 });
    }
    throw error;
  }
}
```

```php PHP theme={null}
use StockShift\Sdk\Exception\WebhookVerificationException;
use StockShift\Sdk\Webhook;

try {
    $event = Webhook::verify(
        file_get_contents('php://input'),
        $_SERVER['HTTP_STOCKSHIFT_SIGNATURE'] ?? null,
        getenv('STOCKSHIFT_WEBHOOK_SECRET'),
    );
} catch (WebhookVerificationException) {
    http_response_code(400);
    exit;
}

http_response_code(204);
```

```python Python theme={null}
from stockshift import WebhookVerificationError, verify_webhook
from stockshift.models import InventoryStockAdjustedEvent


def receive(body: bytes, signature: str | None) -> int:
    try:
        event = verify_webhook(body, signature, os.environ["STOCKSHIFT_WEBHOOK_SECRET"])
    except WebhookVerificationError:
        return 400

    if isinstance(event, InventoryStockAdjustedEvent):
        print(event.data.item_id, event.data.quantity)
    return 204
```

```go Go theme={null}
verifier := stockshift.WebhookVerifier{Secret: os.Getenv("STOCKSHIFT_WEBHOOK_SECRET")}

http.HandleFunc("POST /webhooks/stockshift", func(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "", http.StatusBadRequest)
		return
	}
	event, err := verifier.Verify(body, r.Header.Get(stockshift.SignatureHeader))
	if err != nil {
		http.Error(w, "", http.StatusBadRequest)
		return
	}
	fmt.Println(event.ID, event.Type)
	w.WriteHeader(http.StatusNoContent)
})
```

```rust Rust theme={null}
use stockshift::webhook::{EventData, Webhook};

fn receive(body: &[u8], signature: Option<&str>, secret: &str) -> u16 {
    let Ok(event) = Webhook::new(secret).verify(body, signature) else {
        return 400;
    };
    if let EventData::InventoryStockAdjusted(adjusted) = &event.data {
        println!("{} {}", adjusted.item_id, adjusted.quantity);
    }
    204
}
```

```csharp .NET theme={null}
app.MapPost("/webhooks/stockshift", async (HttpRequest request) =>
{
    using var reader = new StreamReader(request.Body);
    var payload = await reader.ReadToEndAsync();

    try
    {
        var webhookEvent = StockShiftWebhook.Verify(payload, request.Headers[StockShiftWebhook.SignatureHeader], secret);
        return Results.NoContent();
    }
    catch (WebhookVerificationException)
    {
        return Results.BadRequest();
    }
});
```

In a Laravel app, the [Laravel package](https://docs.stockshift.io/sdks/laravel.md#webhooks) adds a route that checks the signature and fires a Laravel event for each delivery.

## Retries

A delivery that gets anything other than a `2xx` is tried again after 1, 5, 30, 120, 360 and 720 minutes. If it still fails, or 24 hours pass, the delivery is marked failed, the endpoint is disabled and the organization's owners get an email. Events that happen while an endpoint is disabled are not sent later.

The **Deliveries** card on an endpoint lists its latest 50 deliveries with their status, response code, time taken and attempts. **Retry** sends a failed delivery once more. Deliveries are kept for 30 days.

To pause an endpoint, untick **Enabled** and press **Save endpoint**.


## Related topics

- [Webhook events](https://docs.stockshift.io/api-reference/webhook-events.md)
- [Ping a webhook endpoint](https://docs.stockshift.io/api-reference/webhooks/ping-a-webhook-endpoint.md)
- [List webhook endpoints](https://docs.stockshift.io/api-reference/webhooks/list-webhook-endpoints.md)


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