# Create a webhook

Sets the webhook of the account, the same one as in the dashboard. job.* events are sent for jobs created with /v2, conversion.* events for conversions created with /v1. An account has one webhook: if it has one already, the answer is 409. signing_secret is only in this response.

`POST https://api.switchfile.io/v2/webhooks`

Needs an API key: `Authorization: Bearer YOUR_API_KEY`. See [Authentication](https://switchfile.io/docs/authentication).

## Example

cURL:

```bash
curl "https://api.switchfile.io/v2/webhooks" \
  -H "Authorization: Bearer $SWITCHFILE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/switchfile",
    "events": [
      "job.finished",
      "job.failed"
    ]
  }'
```

Node.js:

```js
const res = await fetch("https://api.switchfile.io/v2/webhooks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SWITCHFILE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://example.com/hooks/switchfile",
    events: [
      "job.finished",
      "job.failed"
    ]
  }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
console.log(await res.json());
```

PHP:

```php
<?php
$ch = curl_init('https://api.switchfile.io/v2/webhooks');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'url' => 'https://example.com/hooks/switchfile',
        'events' => [
            'job.finished',
            'job.failed',
        ],
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('SWITCHFILE_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status >= 400) {
    throw new RuntimeException("HTTP $status: $response");
}
print_r(json_decode($response, true));
```

Python:

```python
import os
import requests

res = requests.post(
    "https://api.switchfile.io/v2/webhooks",
    headers={"Authorization": f"Bearer {os.environ['SWITCHFILE_API_KEY']}"},
    json={
        "url": "https://example.com/hooks/switchfile",
        "events": [
            "job.finished",
            "job.failed",
        ],
    },
)
res.raise_for_status()
print(res.json())
```

## Request body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `events` | string[] | yes | (at least 1 items) |
| `url` | string | yes | Public http or https URL on port 80 or 443 (1 to 2048 characters) |

## Responses

### 201: Created

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | CreatedWebhook | yes |  |
| `data.created_at` | string | yes | ISO 8601 timestamp, for example 2026-11-06T18:00:00+00:00 |
| `data.disabled` | boolean | yes | Always false: switchfile does not disable webhooks on its own |
| `data.events` | string[] | yes |  |
| `data.failing` | boolean | yes | The last delivery to this URL failed |
| `data.id` | string | yes | ID of the webhook (a UUID; CloudConvert uses an integer) |
| `data.last_error_at` | string \| null | yes | ISO 8601 timestamp, for example 2026-11-06T18:00:00+00:00 |
| `data.last_response_code` | string \| null | yes | HTTP status of the last failed delivery, or NETWORK_ERROR |
| `data.links` | object | yes |  |
| `data.links.self` | string | yes |  |
| `data.signing_secret` | string | yes | Signing secret of the account. Only in this response |
| `data.updated_at` | string | yes | ISO 8601 timestamp, for example 2026-11-06T18:00:00+00:00 |
| `data.url` | string | yes |  |

Example (application/json):

```json
{
  "data": {
    "id": "1a7e3c5b-9d2f-4b6a-8e0c-5f3d1b7a9c24",
    "url": "https://example.com/hooks/switchfile",
    "disabled": false,
    "events": [
      "job.finished",
      "job.failed"
    ],
    "failing": false,
    "last_error_at": null,
    "last_response_code": null,
    "created_at": "2026-11-06T18:00:00+00:00",
    "updated_at": "2026-11-06T18:00:00+00:00",
    "links": {
      "self": "https://api.switchfile.io/v2/webhooks/1a7e3c5b-9d2f-4b6a-8e0c-5f3d1b7a9c24"
    },
    "signing_secret": "your-signing-secret"
  }
}
```

### 401: The API key is missing or not valid (`UNAUTHENTICATED`)

Example (application/json):

```json
{
  "message": "Missing or invalid API key.",
  "code": "UNAUTHENTICATED"
}
```

### 404: Does not exist for this account (`NOT_FOUND`)

Example (application/json):

```json
{
  "message": "Job not found.",
  "code": "NOT_FOUND"
}
```

### 409: The account has a webhook already. Delete it first or change it in the dashboard (`WEBHOOK_EXISTS`)

Example (application/json):

```json
{
  "message": "This account already has a webhook. switchfile has one webhook per account: delete it first or change it in the dashboard.",
  "code": "WEBHOOK_EXISTS"
}
```

### 411: The request has a body but no Content-Length header (`LENGTH_REQUIRED`)

Example (application/json):

```json
{
  "message": "A Content-Length header is required for this request.",
  "code": "LENGTH_REQUIRED"
}
```

### 413: The request or the file is too large (`PAYLOAD_TOO_LARGE`)

Example (application/json):

```json
{
  "message": "The request body is larger than the allowed maximum.",
  "code": "PAYLOAD_TOO_LARGE"
}
```

### 422: The URL is not a public http or https address, or an event is unknown (`INVALID_DATA`)

Example (application/json):

```json
{
  "message": "url: The URL points to a private, reserved or own network address.",
  "code": "INVALID_DATA",
  "errors": {
    "url": [
      "The URL points to a private, reserved or own network address."
    ]
  }
}
```

### 429: Too many requests or a daily quota is used up. See Retry-After (`RATE_LIMIT_EXCEEDED`)

Example (application/json):

```json
{
  "message": "Too many requests. Try again in 12 seconds.",
  "code": "RATE_LIMIT_EXCEEDED"
}
```

Error bodies and codes are explained in [Errors](https://switchfile.io/docs/errors).
