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
| Method | Endpoint | Description | Authentication |
|---|---|---|---|
| POST | /webhook | Trigger a deployment | Webhook signature (optional) |
| GET | /status | Get current deployment status and progress | API key (if configured) |
| GET | /health | Check application and deployment health | API 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"
}
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 initiatedgit_pull- Pulling latest code from repositoryinstalling_dependencies- Installing npm/pip/bundle packagesstopping_app- Stopping current applicationbuilding- Running build commandsstarting_app- Starting new application versionhealth_check- Verifying application healthcompleted- 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
| Variable | Default | Description |
|---|---|---|
WEBHOOK_PORT | 9001 | Port for webhook listener |
WEBHOOK_SECRET | (empty) | Secret for signature verification |
WEBHOOK_SIGNATURE_VERIFY | false | Enable signature verification |
WEBHOOK_PATH | /webhook | Endpoint path |
WEBHOOK_TIMEOUT | 30000 | Request 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
/statusendpoint - 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
- 404 Not Found: Check
WEBHOOK_PATHconfiguration - 401 Unauthorized: Verify webhook secret matches
- 500 Internal Error: Check container logs
- Connection Refused: Ensure port 9001 is exposed
Debug Mode
Enable debug logging:
-e LOG_LEVEL=debug
-e DEBUG=webhook:*