# Webhooks

Subscribe an HTTPS endpoint to signed thin events.

Event Destinations send account events to external systems. FiscalRail currently supports webhook destinations; later destination types can use the same resource.

Read [Receiving events with webhooks](/en/docs/webhooks) for an end-to-end setup, signature verification and processing workflow.

Destinations belong to exactly one Live or Test account. Each account can create up to 20 destinations. A destination can subscribe to up to 20 exact event types, or use `"*"` to receive every event type.

Webhook bodies are thin Events: they contain the event ID, type, occurrence time and related resource, but omit the immutable `data` snapshot. Retrieve the Event or related resource through the API when processing requires more data.

## Verify signatures

Each destination has a readable signing secret beginning with `whsec_`. FiscalRail sends a signature header with every attempt:

```http
FiscalRail-Signature: t=1786451696,v1=4f32...
```

Compute an HMAC-SHA256 using the destination's signing secret over the timestamp, a period, and the exact raw request body:

```text
HMAC-SHA256(secret, timestamp + "." + raw_body)
```

Compare the hexadecimal result with `v1` using a constant-time comparison and reject timestamps more than five minutes from the current time. Verify the raw bytes before parsing JSON. Every retry receives a new timestamp and signature.

Return any HTTP `2xx` response quickly to acknowledge delivery. FiscalRail attempts each event up to five times. Deliveries are at least once and are not ordered, so use the Event ID for deduplication.

If every attempted delivery fails for 24 hours, FiscalRail disables the destination. Fix the endpoint and enable it again through the API or dashboard.

## The Event Destination object

### Properties

#### `id`

Type: `string`

Opaque identifier for the event destination.

#### `object`

Type: `string`

String identifying this as an Event Destination object. Always `event_destination`.

#### `live`

Type: `boolean`

True when the object belongs to the live environment; false for test data.

#### `account`

Type: `string`

Live or Test account that owns this destination.

#### `name`

Type: `string`

Human-readable destination name.

#### `type`

Type: `string`

Transport used by this destination. Currently always webhook. Always `webhook`.

#### `status`

Type: `enum`

Whether matching events are currently delivered.

Possible values:

- `enabled` — 
- `disabled` — 

#### `enabled_events`

Type: `array of strings`

Exact subscribed event types, or a single asterisk for all events.

#### `webhook`

Type: `object`

Webhook-specific URL and signing configuration.

##### `webhook.url`

Type: `string`

Public HTTPS endpoint that receives event deliveries.

##### `webhook.signing_secret`

Type: `string or null`

Readable on create and retrieve; null in list responses.


#### `disabled_reason`

Type: `enum or null`

Why this destination was disabled, or null while enabled.

Possible values:

- `user` — 
- `delivery_failures` — 
- `` — 

#### `created_at`

Type: `string`

When the destination was created.

#### `updated_at`

Type: `string`

When the destination was last updated.


### Example

```json
{
  "id": null,
  "object": "event_destination",
  "live": null,
  "account": "acct_14Vxtqg2nwvPR75TpsGH8N",
  "name": null,
  "type": "webhook",
  "status": null,
  "enabled_events": [],
  "webhook": {
    "url": null,
    "signing_secret": null
  },
  "disabled_reason": null,
  "created_at": null,
  "updated_at": null
}
```

## Create an event destination

`POST /v1/event-destinations`

Creates a webhook endpoint. An account can have at most 20 event destinations.

### Request body

#### `name`

Type: `string` — required

Human-readable destination name.

#### `url`

Type: `string` — required

Public HTTPS endpoint that receives deliveries.

#### `enabled_events`

Type: `array of strings` — required

One to 20 exact event types, or a single asterisk for all events.


### Responses

- `201` — [The created Event Destination, including its signing secret.](#the-event-destination-object) Formats: JSON.

### Example request

```bash
curl --request POST \
  'https://api.fiscalrail.com/v1/event-destinations' \
  --header "Authorization: Bearer ak_test_..."
```

### Example response — 201

```json
{
  "id": null,
  "object": "event_destination",
  "live": null,
  "account": "acct_14Vxtqg2nwvPR75TpsGH8N",
  "name": null,
  "type": "webhook",
  "status": null,
  "enabled_events": [],
  "webhook": {
    "url": null,
    "signing_secret": null
  },
  "disabled_reason": null,
  "created_at": null,
  "updated_at": null
}
```

## Retrieve an event destination

`GET /v1/event-destinations/{id}`

Returns the destination and its readable signing secret.

### Path parameters

#### `id`

Type: `string` — required

The opaque event destination ID.


### Responses

- `200` — [An Event Destination object.](#the-event-destination-object) Formats: JSON.

### Example request

```bash
curl --request GET \
  'https://api.fiscalrail.com/v1/event-destinations/example' \
  --header "Authorization: Bearer ak_test_..."
```

### Example response — 200

```json
{
  "id": null,
  "object": "event_destination",
  "live": null,
  "account": "acct_14Vxtqg2nwvPR75TpsGH8N",
  "name": null,
  "type": "webhook",
  "status": null,
  "enabled_events": [],
  "webhook": {
    "url": null,
    "signing_secret": null
  },
  "disabled_reason": null,
  "created_at": null,
  "updated_at": null
}
```

## Update an event destination

`PATCH /v1/event-destinations/{id}`

### Path parameters

#### `id`

Type: `string` — required

The opaque event destination ID.


### Request body

#### `name`

Type: `string`

Human-readable destination name.

#### `url`

Type: `string`

Public HTTPS endpoint that receives deliveries.

#### `enabled_events`

Type: `array of strings`

One to 20 exact event types, or a single asterisk for all events.


### Responses

- `200` — [The updated Event Destination.](#the-event-destination-object) Formats: JSON.

### Example request

```bash
curl --request PATCH \
  'https://api.fiscalrail.com/v1/event-destinations/example' \
  --header "Authorization: Bearer ak_test_..."
```

### Example response — 200

```json
{
  "id": null,
  "object": "event_destination",
  "live": null,
  "account": "acct_14Vxtqg2nwvPR75TpsGH8N",
  "name": null,
  "type": "webhook",
  "status": null,
  "enabled_events": [],
  "webhook": {
    "url": null,
    "signing_secret": null
  },
  "disabled_reason": null,
  "created_at": null,
  "updated_at": null
}
```

## Enable an event destination

`POST /v1/event-destinations/{id}/enable`

### Path parameters

#### `id`

Type: `string` — required

The opaque event destination ID.


### Responses

- `200` — [The enabled Event Destination.](#the-event-destination-object) Formats: JSON.

### Example request

```bash
curl --request POST \
  'https://api.fiscalrail.com/v1/event-destinations/example/enable' \
  --header "Authorization: Bearer ak_test_..."
```

### Example response — 200

```json
{
  "id": null,
  "object": "event_destination",
  "live": null,
  "account": "acct_14Vxtqg2nwvPR75TpsGH8N",
  "name": null,
  "type": "webhook",
  "status": null,
  "enabled_events": [],
  "webhook": {
    "url": null,
    "signing_secret": null
  },
  "disabled_reason": null,
  "created_at": null,
  "updated_at": null
}
```

## Disable an event destination

`POST /v1/event-destinations/{id}/disable`

### Path parameters

#### `id`

Type: `string` — required

The opaque event destination ID.


### Responses

- `200` — [The disabled Event Destination.](#the-event-destination-object) Formats: JSON.

### Example request

```bash
curl --request POST \
  'https://api.fiscalrail.com/v1/event-destinations/example/disable' \
  --header "Authorization: Bearer ak_test_..."
```

### Example response — 200

```json
{
  "id": null,
  "object": "event_destination",
  "live": null,
  "account": "acct_14Vxtqg2nwvPR75TpsGH8N",
  "name": null,
  "type": "webhook",
  "status": null,
  "enabled_events": [],
  "webhook": {
    "url": null,
    "signing_secret": null
  },
  "disabled_reason": null,
  "created_at": null,
  "updated_at": null
}
```

## Delete an event destination

`DELETE /v1/event-destinations/{id}`

### Path parameters

#### `id`

Type: `string` — required

The opaque event destination ID.


### Responses

- `204` — The destination was deleted.

### Example request

```bash
curl --request DELETE \
  'https://api.fiscalrail.com/v1/event-destinations/example' \
  --header "Authorization: Bearer ak_test_..."
```

## List event destinations

`GET /v1/event-destinations`

Returns destinations for the authenticated Live or Test account. Signing secrets are null in list responses.

### Query parameters

#### `limit`

Type: `integer`

Maximum number of resources to return. Defaults to `25`.

#### `starting_after`

Type: `string`

Return destinations older than this destination ID.

#### `ending_before`

Type: `string`

Return destinations newer than this destination ID.


### Responses

- `200` — [A list of Event Destination objects.](#the-event-destination-object) Formats: JSON.

### Example request

```bash
curl --request GET \
  'https://api.fiscalrail.com/v1/event-destinations?limit=25' \
  --header "Authorization: Bearer ak_test_..."
```

### Example response — 200

```json
{
  "object": "list",
  "has_more": null,
  "data": [
    {
      "id": null,
      "object": "event_destination",
      "live": null,
      "account": "acct_14Vxtqg2nwvPR75TpsGH8N",
      "name": null,
      "type": "webhook",
      "status": null,
      "enabled_events": [],
      "webhook": {
        "url": null,
        "signing_secret": null
      },
      "disabled_reason": null,
      "created_at": null,
      "updated_at": null
    }
  ]
}
```
