Webhooks

Receive real-time HTTP notifications when events happen in your repositories, deployments, and CI/CD pipelines.

Note: Webhooks deliver events asynchronously. For synchronous operations, use the .git REST API.

Configure a webhook endpoint to subscribe to events across your organization. When a subscribed event occurs, .git sends a POST request to your endpoint with a JSON payload containing event metadata and context.

Quick Setup

1. Generate a Secret

Create a x-git-signature secret to verify webhook authenticity. Store it securely in your environment variables.

2. Configure Your Endpoint

Provide an HTTPS URL that accepts POST requests with application/json payloads. Your endpoint must respond with a 2xx status code within 10 seconds.

3. Subscribe to Events

Select the event types you want to receive. You can subscribe to all events or filter by specific scopes like deployments, code_review, or issues.

Available Events

Select from the following event types when configuring your webhook:

🚀 deployment.success POST

Triggered when a deployment completes successfully.

⚠️ deployment.failed POST

Triggered when a deployment fails or rolls back.

📦 push.received POST

Triggered on new commits to subscribed branches.

👥 review.requested POST

Triggered when a PR review is requested or submitted.

🔐 secret.rotated POST

Triggered when environment variables are updated.

🌍 edge.cdn.purge POST

Triggered when the global edge cache is invalidated.

Payload Structure

Every webhook delivery includes a standardized JSON envelope. The data field contains event-specific details.

{
  "id": "evt_8x7k2m9p4q",
  "type": "deployment.success",
  "timestamp": "2025-04-12T14:32:08Z",
  "source": {
    "organization": "acme-corp",
    "repository": "backend-api",
    "branch": "main"
  },
  "data": {
    "deploy_id": "dpl_3n2m1k",
    "url": "https://acme-api.git.dev",
    "region": "us-east-1",
    "duration_ms": 2430,
    "commit_sha": "a1b2c3d4e5"
  }
}

Common Payload Fields

FieldTypeDescription
idstringUnique event identifier
typestringEvent type (e.g., deployment.success)
timestampISO 8601Time the event occurred
sourceobjectContext about the triggering resource
dataobjectEvent-specific payload details

Verification & Security

Every webhook request includes an X-Git-Signature-256 header containing an HMAC-SHA256 hex digest. Verify it to ensure requests originate from .git.

const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(payload).digest('hex');
  const expected = `sha256=${digest}`;
  
  // Constant-time comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature), 
    Buffer.from(expected)
  );
}

// Usage in Express
app.post('/webhook', (req, res) => {
  const sig = req.headers['x-git-signature-256'];
  if (!verifyWebhook(JSON.stringify(req.body), sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  res.status(200).send('OK');
});
import hashlib
import hmac

def verify_webhook(payload, signature, secret):
    digest = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    expected = f"sha256={digest}"
    return hmac.compare_digest(signature, expected)

# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
    sig = request.headers.get('X-Git-Signature-256')
    if not verify_webhook(request.get_data(), sig, os.getenv('WEBHOOK_SECRET')):
        return 'Invalid', 401
    return 'OK', 200
# Simulate a webhook delivery locally
curl -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Git-Signature-256: sha256=8f14e45fceea167a5a36dedd4bea2543" \
  -H "User-Agent: .git-Webhook/1.0" \
  -d '{"type":"deployment.success","id":"evt_test","timestamp":"2025-04-12T14:32:08Z"}'

Delivery & Retries

.git guarantees at-least-once delivery. If your endpoint does not respond with a 2xx status, we retry with exponential backoff:

  • 1st retry: 30 seconds
  • 2nd retry: 2 minutes
  • 3rd retry: 10 minutes
  • 4th retry: 1 hour
  • 5th retry: 6 hours
Tip: Respond quickly with 200 OK even if you process the payload asynchronously. Long-running sync processing will cause timeout retries.

Testing Locally

Use ngrok or localhost.run to expose your local development server to the public internet, then configure the generated URL as your webhook endpoint in the .git Dashboard.

# Start ngrok on port 3000
ngrok http 3000

# Copy the forwarding URL (e.g., https://a1b2c3.ngrok.io)
# Configure it in .git -> Settings -> Webhooks