VECTOR
Vector Pro

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:

  1. Logs the event to your account's event history
  2. Dispatches webhooks to any subscribed endpoints
  3. Retries failed deliveries with exponential backoff

Event Types

Site Events

Event Description
vector.site.pendingSite provisioning has been queued
vector.site.createdSite has been fully provisioned
vector.site.updatedSite settings have been modified
vector.site.deletedSite has been terminated
vector.site.suspendedSite has been suspended
vector.site.unsuspendedSite has been unsuspended

Environment Events

Event Description
vector.environment.createdEnvironment has been created
vector.environment.updatedEnvironment settings have been modified
vector.environment.deletedEnvironment has been deleted
vector.environment.suspendedEnvironment has been suspended
vector.environment.unsuspendedEnvironment has been unsuspended

Deployment Events

Event Description
vector.deployment.startedDeployment has started
vector.deployment.completedDeployment completed successfully
vector.deployment.failedDeployment 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-Signature header
  • 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

  1. Parse the header to extract timestamp (t) and signature (v1)
  2. Build the signed payload: {timestamp}.{raw_request_body}
  3. Compute expected signature: HMAC-SHA256 using your webhook secret
  4. Compare signatures using constant-time comparison
  5. 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
1Immediate0
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.