# Webhooks

> Outbound delivery webhook endpoints.

Delivery webhook endpoints. PostShiba POSTs each delivery event to the url as a one-item array in the SendGrid Event Webhook format. Every POST carries X-Capsule-Timestamp and X-Capsule-Signature, which is sha256= plus the hex HMAC-SHA256 of {timestamp}.{body} keyed with secret. secret is returned on create and show, not on the list. Omit cluster_id and tenant_id to receive the whole team's events, or set one of them to receive only that cluster's or tenant's. Any response outside 2xx is a failure. An event gets up to 6 attempts, retried after about 5 minutes, 30 minutes, 2 hours, 8 hours, and 24 hours, each up to a fifth early, and is then marked dead. Replay dead deliveries from the dashboard. The API has no replay action. After 8 failed attempts in a row, status is down: new events queue instead of sending, and one queued event is retried at next_canary_at, waiting longer the longer the endpoint stays down, up to an hour. The first success sets status back to up and sends the queue. Changing url resets the count. PostShiba sends nothing else to the url: no health checks. send_test posts one signed sample event with "test": true and the X-Capsule-Test: 1 header; a 2xx also sets status to up. status is idle before the first delivery, retrying after a failure, up after a success, and down as above. The url must be public, and https in production.

## GET /api/v1/teams/:obfuscated_team_id/webhook_endpoints

List webhook endpoints. secret is omitted.

```bash request.sh
curl -sS -X GET \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  https://app.postshiba.com/api/v1/teams/KjkAJW/webhook_endpoints
```

```json response.json
[
  {
    "id": "CdFgHj",
    "url": "https://hooks.example.com/capsule",
    "enabled": true,
    "cluster_id": null,
    "tenant_id": null,
    "event_types": [
      "processed",
      "dropped",
      "deferred",
      "bounce",
      "delivered",
      "spamreport",
      "unsubscribe"
    ],
    "last_success_at": null,
    "last_failure_at": null,
    "consecutive_failures": 0,
    "status": "idle",
    "next_canary_at": null
  }
]
```

## GET /api/v1/webhook_endpoints/:id

Get one endpoint, including the signing secret.

- `id`: Public webhook endpoint id

```bash request.sh
curl -sS -X GET \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  https://app.postshiba.com/api/v1/webhook_endpoints/:webhook_endpoint_id
```

```json response.json
{
  "id": "CdFgHj",
  "url": "https://hooks.example.com/capsule",
  "enabled": true,
  "cluster_id": null,
  "tenant_id": null,
  "event_types": [
    "processed",
    "dropped",
    "deferred",
    "bounce",
    "delivered",
    "spamreport",
    "unsubscribe"
  ],
  "last_success_at": null,
  "last_failure_at": null,
  "consecutive_failures": 0,
  "status": "idle",
  "next_canary_at": null,
  "secret": "hex-secret"
}
```

## POST /api/v1/teams/:obfuscated_team_id/webhook_endpoints

Create an endpoint. Optional event_types, cluster_id, and tenant_id. Defaults to the SendGrid delivery set and team scope.

```bash request.sh
curl -sS -X POST \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"webhook_endpoint":{"url":"https://hooks.example.com/capsule","event_types":["delivered","bounce"],"cluster_id":"NmQpXr"}}' \
  https://app.postshiba.com/api/v1/teams/KjkAJW/webhook_endpoints
```

```json response.json
{
  "id": "CdFgHj",
  "url": "https://hooks.example.com/capsule",
  "enabled": true,
  "cluster_id": null,
  "tenant_id": null,
  "event_types": [
    "processed",
    "dropped",
    "deferred",
    "bounce",
    "delivered",
    "spamreport",
    "unsubscribe"
  ],
  "last_success_at": null,
  "last_failure_at": null,
  "consecutive_failures": 0,
  "status": "idle",
  "next_canary_at": null,
  "secret": "hex-secret"
}
```

## PATCH /api/v1/webhook_endpoints/:id

Update url, enabled, event_types, or scope. secret is omitted.

- `id`: Public webhook endpoint id

```bash request.sh
curl -sS -X PATCH \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"webhook_endpoint":{"enabled":false,"event_types":["delivered","bounce"]}}' \
  https://app.postshiba.com/api/v1/webhook_endpoints/:webhook_endpoint_id
```

```json response.json
{
  "id": "CdFgHj",
  "url": "https://hooks.example.com/capsule",
  "enabled": false,
  "cluster_id": null,
  "tenant_id": null,
  "event_types": [
    "delivered",
    "bounce"
  ],
  "last_success_at": null,
  "last_failure_at": null,
  "consecutive_failures": 0,
  "status": "idle",
  "next_canary_at": null
}
```

## DELETE /api/v1/webhook_endpoints/:id

Delete an endpoint.

- `id`: Public webhook endpoint id

```bash request.sh
curl -sS -X DELETE \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  https://app.postshiba.com/api/v1/webhook_endpoints/:webhook_endpoint_id
```

## POST /api/v1/webhook_endpoints/:id/send_test

Post one signed sample event to the url now and return what it answered. ok is true on 2xx.

- `id`: Public webhook endpoint id

```bash request.sh
curl -sS -X POST \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  https://app.postshiba.com/api/v1/webhook_endpoints/:webhook_endpoint_id/send_test
```

```json response.json
{
  "ok": true,
  "response_code": 200,
  "error": null,
  "endpoint": {
    "id": "CdFgHj",
    "url": "https://hooks.example.com/capsule",
    "enabled": true,
    "cluster_id": null,
    "tenant_id": null,
    "event_types": [
      "processed",
      "dropped",
      "deferred",
      "bounce",
      "delivered",
      "spamreport",
      "unsubscribe"
    ],
    "last_success_at": "2026-10-12T02:00:00.000Z",
    "last_failure_at": null,
    "consecutive_failures": 0,
    "status": "up",
    "next_canary_at": null
  }
}
```
