Skip to main content

Webhook API

Complete API reference for Flux-Orbit's webhook endpoints and deployment triggers.

Base URL

http://YOUR_SERVER:9001

Default port is 9001, configurable via WEBHOOK_PORT environment variable.

Available Endpoints

MethodEndpointDescriptionAuthentication
POST/webhookTrigger a deploymentWebhook signature (optional)
GET/statusGet current deployment status and progressAPI key (if configured)
GET/healthCheck application and deployment healthAPI key (if configured)

Authentication

API Key Authentication

The /status and /health endpoints support optional API key authentication via the API_KEY environment variable.

When API_KEY is set:

# Protect API endpoints
docker run -d -e API_KEY=your_secret_key_here ...

# Access with authentication
curl -H "Authorization: Bearer your_secret_key_here" http://localhost:9001/status

When API_KEY is NOT set:

# Endpoints are publicly accessible (default)
curl http://localhost:9001/status # No authentication required

Authentication Methods:

The API accepts the key in the Authorization header using either format:

  • Authorization: Bearer <api_key>
  • Authorization: Token <api_key>

Example Responses:

Unauthorized (401):

{
"error": "Unauthorized",
"message": "Valid API key required"
}
Security Note

The API_KEY is independent from WEBHOOK_SECRET:

  • WEBHOOK_SECRET - Used for webhook signature verification (POST /webhook)
  • API_KEY - Used for API endpoint authentication (GET /status, GET /health)

This separation allows you to share webhook secrets with GitHub/GitLab without exposing API access.

Endpoints

POST /webhook

Trigger a deployment via webhook.

Request

Headers:

Content-Type: application/json
X-GitHub-Event: push (optional)
X-Hub-Signature-256: sha256=... (optional, for verification)

Body:

{
"ref": "refs/heads/main",
"repository": {
"clone_url": "https://github.com/user/repo.git"
},
"after": "commit_sha_here",
"pusher": {
"name": "username"
}
}

Response

Success (200 OK):

{
"status": "success",
"message": "Deployment queued",
"delivery_id": "unique-id-here"
}

Error (400 Bad Request):

{
"status": "error",
"message": "Invalid payload"
}

Signature Verification Failed (401 Unauthorized):

{
"status": "error",
"message": "Webhook signature verification failed"
}

GET /status

Get current deployment status.

Response

Idle State (No Deployment Running):

{
"status": "idle",
"last_deployment": {
"commit": "abc123d",
"commit_full": "abc123def456789",
"updated_at": "2024-01-01 12:00:00"
}
}

Deployment In Progress:

{
"status": "deploying",
"stage": "building",
"commit": "def456a",
"commit_full": "def456abc789012",
"rollback_commit": "abc123d",
"started_at": 1704110400,
"duration_seconds": 120,
"timeout_seconds": 1800,
"progress_percent": 6
}

Deployment Stages

The stage field can have these values during deployment:

  • starting - Deployment initiated
  • git_pull - Pulling latest code from repository
  • installing_dependencies - Installing npm/pip/bundle packages
  • stopping_app - Stopping current application
  • building - Running build commands
  • starting_app - Starting new application version
  • health_check - Verifying application health
  • completed - Deployment successful

GET /health

Check application and deployment health.

Response

Application Healthy:

{
"healthy": true,
"app_status": "running",
"app_port": 3000,
"supervisor_status": "running",
"deployment_in_progress": false,
"deployment_stage": "completed"
}

Application Not Responding:

{
"healthy": false,
"app_status": "not_responding",
"app_port": 3000,
"supervisor_status": "stopped",
"deployment_in_progress": false,
"deployment_stage": "completed"
}

During Deployment:

{
"healthy": true,
"app_status": "running",
"app_port": 3000,
"supervisor_status": "running",
"deployment_in_progress": true,
"deployment_stage": "building"
}

Webhook Payloads by Provider

GitHub

Push Event

{
"ref": "refs/heads/main",
"before": "9049f128...",
"after": "0d1a26e6...",
"repository": {
"id": 186853002,
"name": "my-app",
"full_name": "user/my-app",
"private": false,
"owner": {
"name": "user",
"email": "user@example.com"
},
"clone_url": "https://github.com/user/my-app.git",
"default_branch": "main"
},
"pusher": {
"name": "user",
"email": "user@example.com"
},
"sender": {
"login": "user",
"id": 1234567
},
"commits": [
{
"id": "0d1a26e6...",
"message": "Update app.js",
"timestamp": "2024-01-01T00:00:00Z",
"author": {
"name": "User Name",
"email": "user@example.com"
}
}
]
}

Release Event

{
"action": "published",
"release": {
"id": 1234567,
"tag_name": "v1.0.0",
"target_commitish": "main",
"name": "Version 1.0.0",
"draft": false,
"prerelease": false,
"created_at": "2024-01-01T00:00:00Z",
"published_at": "2024-01-01T00:00:00Z"
},
"repository": {
"clone_url": "https://github.com/user/repo.git"
}
}

GitLab

Push Event

{
"object_kind": "push",
"ref": "refs/heads/main",
"checkout_sha": "da1560886...",
"before": "95790bf891...",
"after": "da1560886...",
"project": {
"id": 15,
"name": "my-app",
"web_url": "https://gitlab.com/user/my-app",
"git_http_url": "https://gitlab.com/user/my-app.git",
"git_ssh_url": "git@gitlab.com:user/my-app.git",
"default_branch": "main"
},
"commits": [
{
"id": "da1560886...",
"message": "Update app.js",
"timestamp": "2024-01-01T00:00:00+00:00",
"author": {
"name": "User Name",
"email": "user@example.com"
}
}
]
}

Bitbucket

Push Event

{
"push": {
"changes": [
{
"new": {
"type": "branch",
"name": "main",
"target": {
"hash": "abc123def456"
}
},
"old": {
"type": "branch",
"name": "main",
"target": {
"hash": "789xyz000111"
}
}
}
]
},
"repository": {
"type": "repository",
"full_name": "user/my-app",
"name": "my-app",
"links": {
"clone": [
{
"name": "https",
"href": "https://bitbucket.org/user/my-app.git"
}
]
}
}
}

Webhook Security

Signature Verification

Flux-Orbit supports signature verification for:

GitHub (HMAC-SHA256)

GitHub sends signature in X-Hub-Signature-256 header:

const crypto = require('crypto');

function verifyGitHubSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload, 'utf8');
const expected = 'sha256=' + hmac.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

GitLab (Token)

GitLab sends token in X-Gitlab-Token header:

function verifyGitLabToken(token, secret) {
return token === secret;
}

IP Whitelisting

Recommended IP ranges to whitelist:

GitHub:

# Get latest from: https://api.github.com/meta
192.30.252.0/22
185.199.108.0/22
140.82.112.0/20

GitLab:

# GitLab.com
35.231.145.151

Manual Trigger Examples

Using cURL

# Check deployment status (no auth)
curl http://localhost:9001/status

# Check deployment status (with API key)
curl -H "Authorization: Bearer your_api_key" http://localhost:9001/status

# Check application health (with API key)
curl -H "Authorization: Bearer your_api_key" http://localhost:9001/health

# Trigger deployment
curl -X POST http://localhost:9001/webhook \
-H "Content-Type: application/json" \
-d '{"ref":"refs/heads/main"}'

# Trigger deployment with webhook signature verification
curl -X POST http://localhost:9001/webhook \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=..." \
-d '{"ref":"refs/heads/main"}'

Using JavaScript

const crypto = require('crypto');
const axios = require('axios');

const baseUrl = 'http://localhost:9001';
const apiKey = 'your_api_key_here'; // Set to null if no auth required

// Create axios instance with optional auth
const apiClient = axios.create({
baseURL: baseUrl,
headers: apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {}
});

// Check deployment status
async function checkStatus() {
const response = await apiClient.get('/status');
console.log('Deployment status:', response.data);
return response.data;
}

// Check health
async function checkHealth() {
const response = await apiClient.get('/health');
console.log('Health:', response.data);
return response.data;
}

// Trigger deployment with authentication
async function triggerDeploy() {
const secret = 'your_webhook_secret';
const payload = JSON.stringify({
ref: 'refs/heads/main',
repository: {
clone_url: 'https://github.com/user/repo.git'
}
});

// Calculate signature
const signature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');

// Send webhook
const response = await axios.post(`${baseUrl}/webhook`, payload, {
headers: {
'Content-Type': 'application/json',
'X-Hub-Signature-256': signature
}
});

console.log('Deployment triggered:', response.data);
return response.data;
}

// Monitor deployment progress
async function monitorDeployment() {
let status = await checkStatus();

while (status.status === 'deploying') {
console.log(`Stage: ${status.stage}, Progress: ${status.progress_percent}%`);
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
status = await checkStatus();
}

console.log('Deployment completed!');
}

// Usage
await triggerDeploy();
await monitorDeployment();

Using Python

import hashlib
import hmac
import json
import requests
import time

BASE_URL = 'http://localhost:9001'
API_KEY = 'your_api_key_here' # Set to None if no auth required

# Create session with optional auth headers
session = requests.Session()
if API_KEY:
session.headers.update({'Authorization': f'Bearer {API_KEY}'})

def check_status():
"""Check deployment status"""
response = session.get(f'{BASE_URL}/status')
data = response.json()
print('Deployment status:', data)
return data

def check_health():
"""Check application health"""
response = session.get(f'{BASE_URL}/health')
data = response.json()
print('Health:', data)
return data

def trigger_deploy():
"""Trigger deployment with authentication"""
secret = b'your_webhook_secret'
payload = {
'ref': 'refs/heads/main',
'repository': {
'clone_url': 'https://github.com/user/repo.git'
}
}

# Calculate signature
payload_bytes = json.dumps(payload).encode('utf-8')
signature = 'sha256=' + hmac.new(
secret,
payload_bytes,
hashlib.sha256
).hexdigest()

# Send webhook
response = requests.post(
f'{BASE_URL}/webhook',
json=payload,
headers={
'X-Hub-Signature-256': signature
}
)

print('Deployment triggered:', response.json())
return response.json()

def monitor_deployment():
"""Monitor deployment progress"""
status = check_status()

while status.get('status') == 'deploying':
stage = status.get('stage', 'unknown')
progress = status.get('progress_percent', 0)
print(f"Stage: {stage}, Progress: {progress}%")

time.sleep(5) # Wait 5 seconds
status = check_status()

print('Deployment completed!')

# Usage
if __name__ == '__main__':
trigger_deploy()
monitor_deployment()

Webhook Configuration

Environment Variables

VariableDefaultDescription
WEBHOOK_PORT9001Port for webhook listener
WEBHOOK_SECRET(empty)Secret for signature verification
WEBHOOK_SIGNATURE_VERIFYfalseEnable signature verification
WEBHOOK_PATH/webhookEndpoint path
WEBHOOK_TIMEOUT30000Request timeout (ms)

Example Configuration

docker run -d \
-e WEBHOOK_PORT=9001 \
-e WEBHOOK_SECRET=your_secret_here \
-e WEBHOOK_SIGNATURE_VERIFY=true \
-e WEBHOOK_PATH=/deploy \
-p 9001:9001 \
runonflux/orbit:latest

Deployment Queue

Flux-Orbit queues deployments to prevent conflicts:

  • Multiple webhooks within 30 seconds are debounced
  • Deployments run sequentially
  • Queue status available at /status endpoint
  • Failed deployments don't block the queue

Queue Management

# View queue status
curl http://localhost:9001/status | jq .queue

# Clear queue (requires restart)
docker restart my-app

Rate Limiting

Flux-Orbit implements basic rate limiting:

  • Max 10 webhook requests per minute per IP
  • Configurable via WEBHOOK_RATE_LIMIT
  • Returns 429 Too Many Requests when exceeded

Monitoring

Using Status API

# Without authentication (if API_KEY not set)
curl http://localhost:9001/status | jq
curl http://localhost:9001/health | jq

# With authentication (if API_KEY is set)
API_KEY="your_api_key_here"
curl -H "Authorization: Bearer $API_KEY" http://localhost:9001/status | jq
curl -H "Authorization: Bearer $API_KEY" http://localhost:9001/health | jq

# Monitor deployment progress with authentication
API_KEY="your_api_key_here"
watch -n 2 "curl -s -H 'Authorization: Bearer $API_KEY' http://localhost:9001/status | jq"

# Monitor both status and health with authentication
API_KEY="your_api_key_here"
while true; do
echo "=== Status ==="
curl -s -H "Authorization: Bearer $API_KEY" http://localhost:9001/status | jq
echo "=== Health ==="
curl -s -H "Authorization: Bearer $API_KEY" http://localhost:9001/health | jq
sleep 5
done

Webhook Logs

# View webhook activity
docker exec my-app tail -f /app/logs/webhook.log

# Count webhook calls
docker exec my-app grep "Webhook received" /app/logs/webhook.log | wc -l

# Failed verifications
docker exec my-app grep "verification failed" /app/logs/webhook.log

Deployment Monitoring

# Watch deployment progress in real-time (with auth)
API_KEY="your_api_key_here"
while true; do
STATUS=$(curl -s -H "Authorization: Bearer $API_KEY" http://localhost:9001/status)
STAGE=$(echo $STATUS | jq -r '.stage // "idle"')
PROGRESS=$(echo $STATUS | jq -r '.progress_percent // 0')
echo "$(date): Stage=$STAGE Progress=${PROGRESS}%"
sleep 2
done

# Alert when deployment completes (with auth)
API_KEY="your_api_key_here"
while true; do
STATUS=$(curl -s -H "Authorization: Bearer $API_KEY" http://localhost:9001/status | jq -r '.status')
if [ "$STATUS" != "deploying" ]; then
echo "Deployment completed!"
# Send notification (Slack, email, etc.)
break
fi
sleep 10
done

Metrics

Track these metrics for monitoring:

  • Webhook requests per minute
  • Successful deployments
  • Failed deployments
  • Average deployment time
  • Queue size
  • Deployment stage durations
  • Health check success rate

Troubleshooting

Common Issues

  1. 404 Not Found: Check WEBHOOK_PATH configuration
  2. 401 Unauthorized: Verify webhook secret matches
  3. 500 Internal Error: Check container logs
  4. Connection Refused: Ensure port 9001 is exposed

Debug Mode

Enable debug logging:

-e LOG_LEVEL=debug
-e DEBUG=webhook:*

Next Steps