Skip to main content

Documentation Index

Fetch the complete documentation index at: https://code.storage/docs/llms.txt

Use this file to discover all available pages before exploring further.

Code Storage provides a webhook system that allows you to receive real-time notifications about Git events across your storage layer. This enables you to integrate Code Storage deeply within your product, CI/CD pipelines, monitoring systems, and more.

How webhooks work

Webhooks are HTTP POST requests sent to your specified endpoint whenever certain events occur in your repositories. When you create a webhook subscription, Code Storage will:
  1. Monitor Events: Watch for the events you’ve subscribed to (e.g., push or repo.sync.* events)
  2. Generate Payloads: Create JSON payloads containing event details
  3. Sign Requests: Add cryptographic signatures for security verification
  4. Deliver Webhooks: Send HTTP POST requests to your endpoint with automatic retries

Webhook headers

Every webhook request includes the following headers:
  • Content-Type: application/json
  • User-Agent: Pierre-Webhook/1.0
  • X-Pierre-Event: <event_type> (e.g., push, repo.sync.started, repo.sync.succeeded, repo.sync.failed)
  • X-Pierre-Signature: t=1642678200,sha256=abc123... (security signature)

Event types

push

Triggered when commits are pushed to a repository.
{
  "repository": {
    "id": "repo_01HZXE4K6YZ1Q2ABCD3EFG45H6",
    "url": "team/project-alpha"
  },
  "ref": "refs/heads/main",
  "before": "abc123def456...",
  "after": "def456abc123...",
  "customer_id": "your-customer-id",
  "pushed_at": "2024-01-20T10:30:00Z"
}

repo.sync.started

Triggered when a repository sync run begins, before the upstream fetch executes. Useful for tracking sync progress or updating status indicators in your UI.
{
  "type": "repo.sync.started",
  "repository": {
    "id": "repo_01HZXE4K6YZ1Q2ABCD3EFG45H6",
    "url": "team/project-alpha"
  },
  "run_count": 1,
  "is_first_sync": true,
  "started_at": "2026-03-17T10:00:00Z"
}

repo.sync.succeeded

Triggered when a sync run completes successfully.
{
  "type": "repo.sync.succeeded",
  "repository": {
    "id": "repo_01HZXE4K6YZ1Q2ABCD3EFG45H6",
    "url": "team/project-alpha"
  },
  "run_count": 1,
  "is_first_sync": true,
  "started_at": "2026-03-17T10:00:00Z",
  "completed_at": "2026-03-17T10:02:30Z"
}

repo.sync.failed

Triggered when a sync run fails, including mirror activity failures (after retries are exhausted) and infrastructure-level failures.
{
  "type": "repo.sync.failed",
  "repository": {
    "id": "repo_01HZXE4K6YZ1Q2ABCD3EFG45H6",
    "url": "team/project-alpha"
  },
  "run_count": 1,
  "is_first_sync": true,
  "started_at": "2026-03-17T10:00:00Z",
  "completed_at": "2026-03-17T10:02:30Z",
  "error": "authentication failed"
}

Sync event fields

FieldDescription
run_countFor started, the number of previously completed runs. For succeeded/failed, includes the run that just finished.
is_first_synctrue if the repository had never successfully synced before this run began. Useful for detecting initial sync completion without comparing run counts.
errorPresent only on failed events. Possible values: "authentication failed", "failed to clone repository", "failed to push to storage", "repository configuration error", "upstream repository unreachable", "internal error", "session unavailable", "sync failed".
Cancellations triggered internally (e.g., when a repository is detached from its upstream) do not produce a repo.sync.failed event.

Securing webhooks

To ensure the webhooks you receive are legitimate and from Code Storage, you must verify the HMAC signature included with each webhook delivery.

HMAC Signature Verification

Each webhook includes an X-Pierre-Signature header with the format:
X-Pierre-Signature: t=<unix_timestamp>,sha256=<hex_signature>
The signature is computed as:
HMAC-SHA256(webhook_secret, timestamp + "." + payload)

Webhook SDK methods

The SDK provides helper methods to help you validate webhook events quickly.
import { validateWebhook } from '@pierre/storage';

async function handleWebhookRequest(request) {
  // Get the raw request body as text
  const payload = await request.text();

  // Validate the webhook using the SDK
  const result = await validateWebhook(
    payload,
    request.headers, // Headers object with x-pierre-signature and x-pierre-event
    process.env.WEBHOOK_SECRET,
  );

  if (!result.valid) {
    console.error('Invalid webhook:', result.error);
    return new Response('Invalid webhook', { status: 401 });
  }

  // Route by event type
  switch (result.eventType) {
    case 'push':
      console.log(`Push to ${result.payload.ref}`);
      console.log(`Commit: ${result.payload.before} -> ${result.payload.after}`);
      break;
    case 'repo.sync.started':
    case 'repo.sync.succeeded':
    case 'repo.sync.failed': {
      // Sync events are delivered as raw JSON — parse the validated payload
      const sync = JSON.parse(payload);
      console.log(`${result.eventType} for ${sync.repository.id}`);
      if (sync.error) {
        console.log(`Error: ${sync.error}`);
      }
      break;
    }
  }

  return new Response('OK', { status: 200 });
}

Advanced SDK usage

Custom Validation Options:
import { validateWebhook } from '@pierre/storage';

const result = await validateWebhook(payload, headers, webhookSecret, {
  maxAgeSeconds: 600, // Allow webhooks up to 10 minutes old (default: 300)
  // maxAgeSeconds: 0  // Disable timestamp validation entirely
});
Signature-Only Validation for cases where you need more control over the validation process:
import { validateWebhookSignature, parseSignatureHeader } from '@pierre/storage';

// Just validate the signature without parsing the payload
const result = await validateWebhookSignature(
  payload,
  headers['x-pierre-signature'],
  webhookSecret,
);

if (result.valid) {
  // Manually parse and process the payload as needed
  const eventType = headers['x-pierre-event'];
  const webhookData = JSON.parse(payload);
  // ... custom processing logic
}

Common verification errors

When using the SDK, these errors are automatically detected and returned in the result.error field:
  • Missing signature components: “Invalid signature header format”
  • Timestamp too old: “Webhook timestamp too old (X seconds)”
  • Future timestamp: “Webhook timestamp is in the future”
  • Signature mismatch: “Invalid signature”
  • Invalid JSON: “Invalid JSON payload” (when using validateWebhook)
  • Missing headers: “Missing or invalid X-Pierre-Signature header”
Error Handling Best Practices:
import { validateWebhook } from '@pierre/storage';

const result = await validateWebhook(payload, headers, secret);

if (!result.valid) {
  // Log the error for debugging (don't expose to clients)
  console.error('Webhook validation failed:', result.error, {
    timestamp: result.timestamp,
    eventType: result.eventType,
    // Don't log payload or secret for security
  });

  // Return appropriate HTTP status codes
  if (result.error?.includes('timestamp')) {
    return new Response('Request too old', { status: 408 }); // Request Timeout
  }

  return new Response('Invalid webhook signature', { status: 401 });
}