Webhooks
Register webhook endpoints for Vector Pro site events, read their delivery logs, and rotate the secret used to sign payloads.
Manage webhooks that receive site event notifications. Webhooks are called when events occur on your sites (deployments, status changes, etc.).
Supports both HTTP webhooks and Slack webhooks for notifications.
Webhook Signature Verification
HTTP webhook payloads include an X-Vector-Signature header for verifying
authenticity. The header format is:
X-Vector-Signature: t=1234567890,v1=abc123...To verify a webhook:
- Parse the timestamp (
t) and signature (v1) from the header - Construct the signed message:
{timestamp}.{raw_request_body} - Compute HMAC-SHA256 using your webhook secret as the key
- Compare with the provided signature using constant-time comparison
- Validate timestamp is within tolerance (recommended: 5 minutes)
Example verification (PHP):
$header = $request->header('X-Vector-Signature');
preg_match('/t=(\d+),v1=([a-f0-9]+)/', $header, $matches);
$timestamp = $matches[1];
$signature = $matches[2];
$message = $timestamp . '.' . $request->getContent();
$expected = hash_hmac('sha256', $message, $webhookSecret);
if (!hash_equals($expected, $signature)) {
throw new Exception('Invalid signature');
}
if (abs(time() - (int)$timestamp) > 300) {
throw new Exception('Timestamp too old');
}List Webhooks
Retrieves a paginated list of all webhooks for the authenticated account. Webhook secrets are never included in list responses.
Query Parameters
| Name | Type | Description |
|---|---|---|
per_page optional | integer | Number of items per page (max 100). Default: 15. Example: 25 |
page optional | integer | Page number for pagination. Default: 1. Example: 1 |
Request
curl -X GET \
"https://api.builtfast.com/api/v1/vector/webhooks?per_page=25&page=1" \
-H "Authorization: Bearer $API_KEY" \
-H "Accept: application/json"$webhooks = $client->webhooks->list();const webhooks = await client.webhooks.list({
per_page: 25,
page: 1,
});Response
{
"data": [
{
"id": "01JFGXK4NQRST5VWX9YZ0ABCDI",
"account_id": 1,
"type": "http",
"type_label": "Http",
"url": "https://example.com/webhook",
"events": [
"site.created",
"deployment.completed"
],
"enabled": true,
"created_at": "2025-05-27T09:50:21+00:00",
"updated_at": "2025-05-27T09:50:21+00:00"
}
],
"links": {},
"meta": {},
"message": "Webhooks retrieved successfully",
"http_status": 200
}{
"data": {},
"message": "Unauthenticated",
"http_status": 401
}Get Webhook
Retrieves details of a specific webhook. The secret is never included in this response - use the rotate-secret endpoint if you need a new secret.
URL Parameters
| Name | Type | Description |
|---|---|---|
webhook required | string | The webhook ID. Example: "01jfgxk4nqrst5vwx9yz0abcdi" |
Request
curl -X GET \
"https://api.builtfast.com/api/v1/vector/webhooks/01jfgxk4nqrst5vwx9yz0abcdi" \
-H "Authorization: Bearer $API_KEY" \
-H "Accept: application/json"$webhook = $client->webhooks->get('01jfgxk4nqrst5vwx9yz0abcdi');const webhook = await client.webhooks.get('01jfgxk4nqrst5vwx9yz0abcdi');Response
{
"data": {
"id": "01JFGXK4NQRST5VWX9YZ0ABCDI",
"account_id": 1,
"type": "http",
"type_label": "Http",
"url": "https://example.com/webhook",
"events": [
"site.created",
"deployment.completed"
],
"enabled": true,
"created_at": "2025-05-27T09:50:21+00:00",
"updated_at": "2025-05-27T09:50:21+00:00"
},
"message": "Webhook retrieved successfully",
"http_status": 200
}{
"data": {},
"message": "Unauthenticated",
"http_status": 401
}{
"data": {},
"message": "Webhook not found",
"http_status": 404
}Create Webhook
Creates a new webhook for the current account.
For HTTP webhooks, the secret is returned ONLY in this response. Store it securely—it cannot be retrieved again (only rotated via the rotate-secret endpoint).
Slack webhooks do not require secrets as they use URL-based authentication.
Body Parameters
| Name | Type | Description |
|---|---|---|
type optional | string | The webhook type. Options: Example: "http" |
url required | string | The webhook URL. For HTTP: must use HTTPS. For Slack: must be a Slack webhook URL. Example: "https://example.com/webhook" |
events required | string[] | Array of events to subscribe to. See available events below. Example: ["site.created","deployment.completed"] |
enabled optional | boolean | Whether the webhook is enabled. Default: true. Example: true |
Request
curl -X POST \
"https://api.builtfast.com/api/v1/vector/webhooks" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"type":"http","url":"https://example.com/webhook","events":["site.created","deployment.completed"],"enabled":true}'$webhook = $client->webhooks->create([
'type' => 'http',
'url' => 'https://example.com/webhook',
'events' => ['site.created', 'deployment.completed'],
'enabled' => true,
]);const webhook = await client.webhooks.create({
type: 'http',
url: 'https://example.com/webhook',
events: ['site.created', 'deployment.completed'],
enabled: true,
});Response
{
"data": {
"id": "01JFGXK4NQRST5VWX9YZ0ABCDI",
"account_id": 1,
"type": "http",
"type_label": "Http",
"url": "https://example.com/webhook",
"events": [
"site.created",
"deployment.completed"
],
"secret": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
"enabled": true,
"created_at": "2025-05-27T09:50:21+00:00",
"updated_at": "2025-05-27T09:50:21+00:00"
},
"message": "Webhook created successfully. Store the secret securely - it will not be shown again.",
"http_status": 201
}{
"data": {
"id": "01JFGXK4NQRST5VWX9YZ0ABCDI",
"account_id": 1,
"type": "slack",
"type_label": "Slack",
"url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
"events": [
"deployment.completed",
"deployment.failed"
],
"enabled": true,
"created_at": "2025-05-27T09:50:21+00:00",
"updated_at": "2025-05-27T09:50:21+00:00"
},
"message": "Slack webhook created successfully.",
"http_status": 201
}{
"data": {},
"message": "Unauthenticated",
"http_status": 401
}{
"data": {},
"errors": {
"events": [
"At least one event must be specified."
]
},
"message": "Validation failed",
"http_status": 422
}Update Webhook
Updates an existing webhook configuration. The secret cannot be updated through this endpoint - use the rotate-secret endpoint instead.
The webhook type cannot be changed after creation.
URL Parameters
| Name | Type | Description |
|---|---|---|
webhook required | string | The webhook ID. Example: "01jfgxk4nqrst5vwx9yz0abcdi" |
Body Parameters
| Name | Type | Description |
|---|---|---|
url optional | string | The webhook URL. Must use HTTPS for HTTP webhooks. Example: "https://example.com/new-webhook" |
events optional | string[] | Array of events to subscribe to. Example: ["site.created","site.updated"] |
enabled optional | boolean | Whether the webhook is enabled. Example: false |
Request
curl -X PUT \
"https://api.builtfast.com/api/v1/vector/webhooks/01jfgxk4nqrst5vwx9yz0abcdi" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"url":"https://example.com/new-webhook","events":["site.created","site.updated"],"enabled":false}'$webhook = $client->webhooks->update(
'01jfgxk4nqrst5vwx9yz0abcdi',
[
'url' => 'https://example.com/new-webhook',
'events' => ['site.created', 'site.updated'],
'enabled' => false,
]
);const webhook = await client.webhooks.update(
'01jfgxk4nqrst5vwx9yz0abcdi',
{
url: 'https://example.com/new-webhook',
events: ['site.created', 'site.updated'],
enabled: false,
}
);Response
{
"data": {
"id": "01JFGXK4NQRST5VWX9YZ0ABCDI",
"account_id": 1,
"type": "http",
"type_label": "Http",
"url": "https://example.com/new-webhook",
"events": [
"site.created",
"site.updated"
],
"enabled": false,
"created_at": "2025-05-27T09:50:21+00:00",
"updated_at": "2025-05-27T10:30:45+00:00"
},
"message": "Webhook updated successfully",
"http_status": 200
}{
"data": {},
"message": "Unauthenticated",
"http_status": 401
}{
"data": {},
"message": "Webhook not found",
"http_status": 404
}{
"data": {},
"errors": {
"events": [
"At least one event must be specified."
]
},
"message": "Validation failed",
"http_status": 422
}Delete Webhook
Deletes a webhook. All associated delivery logs will also be deleted. This operation is irreversible.
URL Parameters
| Name | Type | Description |
|---|---|---|
webhook required | string | The webhook ID. Example: "01jfgxk4nqrst5vwx9yz0abcdi" |
Request
curl -X DELETE \
"https://api.builtfast.com/api/v1/vector/webhooks/01jfgxk4nqrst5vwx9yz0abcdi" \
-H "Authorization: Bearer $API_KEY" \
-H "Accept: application/json"$response = $client->webhooks->delete('01jfgxk4nqrst5vwx9yz0abcdi');const response = await client.webhooks.delete('01jfgxk4nqrst5vwx9yz0abcdi');Response
{
"data": {
"id": "01JFGXK4NQRST5VWX9YZ0ABCDI",
"account_id": 1,
"type": "http",
"type_label": "Http",
"url": "https://example.com/webhook",
"events": [
"site.created"
],
"enabled": true,
"created_at": "2025-05-27T09:50:21+00:00",
"updated_at": "2025-05-27T09:50:21+00:00"
},
"message": "Webhook deleted successfully",
"http_status": 200
}{
"data": {},
"message": "Unauthenticated",
"http_status": 401
}{
"data": {},
"message": "Webhook not found",
"http_status": 404
}List Logs
Retrieves a paginated list of delivery logs for a specific webhook. Logs are returned in reverse chronological order (newest first).
Each log entry includes the event type, request payload, response details, and latency information. Logs are automatically pruned after 90 days.
URL Parameters
| Name | Type | Description |
|---|---|---|
webhook required | string | The webhook ID. Example: "01jfgxk4nqrst5vwx9yz0abcdi" |
Query Parameters
| Name | Type | Description |
|---|---|---|
per_page optional | integer | Number of items per page (max 100). Default: 15. Example: 25 |
page optional | integer | Page number for pagination. Default: 1. Example: 1 |
Request
curl -X GET \
"https://api.builtfast.com/api/v1/vector/webhooks/01jfgxk4nqrst5vwx9yz0abcdi/logs?per_page=25&page=1" \
-H "Authorization: Bearer $API_KEY" \
-H "Accept: application/json"$response = $client->webhooks->listLogs(
'01jfgxk4nqrst5vwx9yz0abcdi',
[
'per_page' => 25,
'page' => 1,
]
);const response = await client.webhooks.listLogs(
'01jfgxk4nqrst5vwx9yz0abcdi',
{
per_page: 25,
page: 1,
}
);Response
{
"data": [
{
"id": "01JFGXK4NQRST5VWX9YZ0ABCDJ",
"vector_webhook_delivery_id": "01JFGXK4NQRST5VWX9YZ0ABCDK",
"vector_webhook_id": "01JFGXK4NQRST5VWX9YZ0ABCDI",
"event": "vector.site.created",
"request_body": {
"event": "vector.site.created",
"data": {}
},
"attempt": 1,
"response_status": 200,
"response_body": "{\"success\": true}",
"response_headers": {
"content-type": "application/json"
},
"latency_ms": 150,
"created_at": "2025-05-27T09:50:21+00:00"
}
],
"links": {},
"meta": {},
"message": "Webhook logs retrieved successfully",
"http_status": 200
}{
"data": {},
"message": "Unauthenticated",
"http_status": 401
}{
"data": {},
"message": "Webhook not found",
"http_status": 404
}Rotate Secret
Generates a new secret for the webhook. The old secret is immediately invalidated and any pending deliveries will use the new secret.
The new secret is returned ONLY in this response. Store it securely—it cannot be retrieved again.
This endpoint is only available for HTTP webhooks. Slack webhooks do not use secrets.
URL Parameters
| Name | Type | Description |
|---|---|---|
webhook required | string | The webhook ID. Example: "01jfgxk4nqrst5vwx9yz0abcdi" |
Request
curl -X POST \
"https://api.builtfast.com/api/v1/vector/webhooks/01jfgxk4nqrst5vwx9yz0abcdi/rotate-secret" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json"$webhook = $client->webhooks->rotateSecret('01jfgxk4nqrst5vwx9yz0abcdi');const webhook = await client.webhooks.rotateSecret('01jfgxk4nqrst5vwx9yz0abcdi');Response
{
"data": {
"secret": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd"
},
"message": "Webhook secret rotated successfully. Store the new secret securely - it will not be shown again.",
"http_status": 200
}{
"data": {},
"message": "Unauthenticated",
"http_status": 401
}{
"data": {},
"message": "Webhook not found",
"http_status": 404
}