Webhooks & Events
Real-time event system that notifies your applications when things happen on your account. Use webhooks to automate workflows, sync data, and build integrations.
Overview
When events occur in Vector—site creation, deployments, environment changes—the system:
- Logs the event to your account's event history
- Dispatches webhooks to any subscribed endpoints
- Retries failed deliveries with exponential backoff
Event Types
Site Events
| Event | Description |
|---|---|
vector.site.pending | Site provisioning has been queued |
vector.site.created | Site has been fully provisioned |
vector.site.updated | Site settings have been modified |
vector.site.deleted | Site has been terminated |
vector.site.suspended | Site has been suspended |
vector.site.unsuspended | Site has been unsuspended |
Environment Events
| Event | Description |
|---|---|
vector.environment.created | Environment has been created |
vector.environment.updated | Environment settings have been modified |
vector.environment.deleted | Environment has been deleted |
vector.environment.suspended | Environment has been suspended |
vector.environment.unsuspended | Environment has been unsuspended |
Deployment Events
| Event | Description |
|---|---|
vector.deployment.started | Deployment has started |
vector.deployment.completed | Deployment completed successfully |
vector.deployment.failed | Deployment failed |
Webhook Types
Vector supports two webhook types:
HTTP Webhooks
Standard webhooks that POST JSON payloads to your HTTPS endpoint. Includes a cryptographic signature for verification.
- URL requirement: Must use HTTPS
- Authentication: HMAC-SHA256 signature in
X-Vector-Signatureheader - Content-Type:
application/json
Slack Webhooks
Native Slack integration that formats events as rich Block Kit messages.
- URL requirement: Must be a Slack incoming webhook URL
- Authentication: URL-based (no secret required)
- Format: Slack Block Kit with fields and context
Webhook Payload Structure
HTTP webhooks receive JSON payloads with this structure:
{
"event": "vector.site.created",
"occurred_at": "2025-01-20T14:30:00+00:00",
"data": {
"id": "01JFGXK4NQRST5VWX9YZ0ABCDE",
"type": "vector_site",
"domain": "happy-panda.yourpartnerdomain.com",
"name": "My Site",
"region": "us-east-1"
}
}
Signature Verification
Always verify webhook authenticity using the X-Vector-Signature header. This prevents attackers from spoofing webhook requests.
Header Format
X-Vector-Signature: t=1705762200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Verification Steps
- Parse the header to extract timestamp (
t) and signature (v1) - Build the signed payload:
{timestamp}.{raw_request_body} - Compute expected signature: HMAC-SHA256 using your webhook secret
- Compare signatures using constant-time comparison
- Validate timestamp is within acceptable tolerance (recommended: 5 minutes)
PHP Example
function verifyWebhookSignature(Request $request, string $secret): void
{
$header = $request->header('X-Vector-Signature');
if (!preg_match('/t=(\d+),v1=([a-f0-9]+)/', $header, $matches)) {
throw new Exception('Invalid signature header format');
}
$timestamp = $matches[1];
$signature = $matches[2];
// Build signed payload
$signedPayload = $timestamp . '.' . $request->getContent();
// Compute expected signature
$expected = hash_hmac('sha256', $signedPayload, $secret);
// Constant-time comparison
if (!hash_equals($expected, $signature)) {
throw new Exception('Invalid signature');
}
// Validate timestamp (5 minute tolerance)
if (abs(time() - (int) $timestamp) > 300) {
throw new Exception('Request timestamp outside tolerance');
}
}
Node.js Example
const crypto = require('crypto');
function verifyWebhookSignature(req, secret) {
const header = req.headers['x-vector-signature'];
const match = header.match(/t=(\d+),v1=([a-f0-9]+)/);
if (!match) {
throw new Error('Invalid signature header format');
}
const [, timestamp, signature] = match;
// Build signed payload
const signedPayload = `${timestamp}.${req.rawBody}`;
// Compute expected signature
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Constant-time comparison
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
throw new Error('Invalid signature');
}
// Validate timestamp (5 minute tolerance)
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
throw new Error('Request timestamp outside tolerance');
}
}
Retry Behavior
Failed webhook deliveries are automatically retried with exponential backoff.
| Attempt | Delay | Cumulative Time |
|---|---|---|
| 1 | Immediate | 0 |
| 2 | ~42 seconds | ~42 seconds |
| 3 | ~2.5 minutes | ~3 minutes |
| 4 | ~9 minutes | ~12 minutes |
| 5 | ~30 minutes | ~42 minutes |
Maximum attempts: 5
Best Practices
Respond Quickly
Return a 2xx response as quickly as possible. Process the webhook payload asynchronously to avoid timeouts.
// Good: Queue for async processing
public function handleWebhook(Request $request)
{
ProcessWebhookJob::dispatch($request->all());
return response()->json(['received' => true]);
}
Handle Duplicates
Webhooks may be delivered more than once. Use the X-Vector-Delivery header to deduplicate.
Protect Your Secret
- Store your webhook secret securely (environment variables, secrets manager)
- Rotate secrets periodically using the rotate-secret endpoint
- Never log or expose the webhook secret
Creating a Webhook
curl -X POST https://api.builtfast.com/api/v1/webhooks \
-H "Authorization: Bearer {your-api-token}" \
-H "Content-Type: application/json" \
-d '{
"type": "http",
"url": "https://your-app.com/webhooks/vector",
"events": [
"vector.site.created",
"vector.deployment.completed",
"vector.deployment.failed"
],
"enabled": true
}'
Important
The secret is only returned when creating a webhook or rotating the secret. Store it securely—you won't see it again.
Creating a Slack Webhook
curl -X POST https://api.builtfast.com/api/v1/webhooks \
-H "Authorization: Bearer {your-api-token}" \
-H "Content-Type: application/json" \
-d '{
"type": "slack",
"url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
"events": [
"vector.deployment.completed",
"vector.deployment.failed"
],
"enabled": true
}'
Rotating a Webhook Secret
If your secret is compromised, rotate it immediately:
curl -X POST "https://api.builtfast.com/api/v1/webhooks/{webhook_id}/rotate-secret" \
-H "Authorization: Bearer {your-api-token}"
The old secret is invalidated immediately. Update your webhook handler with the new secret before the next delivery.
Disabling a Webhook
curl -X PUT "https://api.builtfast.com/api/v1/webhooks/{webhook_id}" \
-H "Authorization: Bearer {your-api-token}" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
Disabled webhooks will not receive new deliveries but can be re-enabled at any time.