comfy-workflow-api
HTTP API for ComfyUI with webhook-based workflow execution.
ComfyUI Workflow API
HTTP API for ComfyUI with webhook-based workflow execution. Submit workflows via HTTP, receive real-time progress updates and outputs at your callback URL.
Installation
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/isala404/comfy-workflow-api.git
pip install -r comfy-workflow-api/requirements.txt
Use Cases
- Backend Integration: Run ComfyUI as a headless service behind your application
- Batch Processing: Submit multiple workflows and receive results asynchronously
- Distributed Systems: Decouple workflow submission from result handling
- Mobile/Web Apps: Trigger image generation from any HTTP client
How It Works
Client ComfyUI Your Server
│ │ │
├─── POST /api/webhook ─────────►│ │
│ (workflow + callback_url) │ │
│ │ │
│◄── {request_id, prompt_id} ────┤ │
│ │ │
│ ├── workflow.progress ──────────►│
│ ├── workflow.progress ──────────►│
│ ├── ... │
│ │ │
│ ├── multipart (files) ──────────►│
│ │ │
Quick Start
1. Create a workflow using webhook nodes
Use WebhookReceiver → WebhookTransformer → WebhookSend to read inputs from HTTP and send outputs back:
┌─► WebhookTransformer ──► CLIPTextEncode ──┐
WebhookReceiver ────┤ (extracts "prompt") ├──► KSampler ──► WebhookSend
└─► WebhookTransformer ──► VAEEncode ───────┘
(extracts "image")
2. Submit via HTTP
# Image-to-image example
curl -X POST http://localhost:8188/api/webhook \
-F "[email protected]" \
-F "callback_url=http://your-server.com/webhook" \
-F "prompt=turn it into a watercolor painting" \
-F "[email protected]"
3. Receive at your callback
Your server receives:
- Progress events (JSON):
{"event": "workflow.progress", "progress": {"value": 5, "max": 20}} - Output files (gzip multipart): Images, audio, video, etc.
Nodes
WebhookReceiver
Entry point for webhook workflows. Reads request configuration from the HTTP call.
| Output | Type | Description | |--------|------|-------------| | webhook_context | WEBHOOK_CONTEXT | Context for other webhook nodes |
WebhookTransformer
Extracts a field from the HTTP request. Connects to any input type.
| Input | Description | |-------|-------------| | webhook_context | From WebhookReceiver | | field | Field name to extract (e.g., "prompt") | | default_value | Optional fallback if field missing |
| Output | Type | Description | |--------|------|-------------| | value | * (wildcard) | Extracted value, auto-typed |
Type inference: Strings are converted to INT, FLOAT, BOOLEAN when applicable. File uploads become IMAGE, AUDIO, VIDEO, MESH, or LATENT based on content type.
WebhookSend
Sends outputs to the callback URL. Supports 5 output fields.
| Input | Description | |-------|-------------| | webhook_context | From WebhookReceiver | | field_1..5 | Any ComfyUI type (IMAGE, AUDIO, STRING, etc.) | | field_1..5_name | Names for each output |
Encoding: IMAGE→PNG, AUDIO→FLAC, VIDEO→MP4, MESH→GLB, LATENT→safetensors
API Reference
POST /api/webhook
| Field | Required | Description | |-------|----------|-------------| | workflow | Yes | Workflow JSON | | callback_url | Yes | URL to receive events/outputs | | auth_header | No | Auth header name | | auth_value | No | Auth header value | | timeout | No | HTTP timeout (default: 60s) | | max_retries | No | Retry attempts (default: 3) | | custom fields | No | Any field accessible via WebhookTransformer |
Response:
{"request_id": "...", "prompt_id": "...", "status": "queued"}
GET /api/webhook/status/{request_id}
Check request status.
DELETE /api/webhook/{request_id}
Cancel a queued/running workflow.
Webhook Events
| Event | Content-Type | Description | |-------|--------------|-------------| | workflow.started | application/json | Execution began | | workflow.progress | application/json | Step progress (throttled 500ms) | | workflow.completed | multipart/form-data (gzip) | Output files + metadata | | workflow.error | application/json | Execution failed |
Note: Completion payload is gzip compressed. Decompress before parsing.
Example
workflow.json (see example_workflow.json):
{
"10": {
"class_type": "WebhookReceiver",
"inputs": {"debug": true}
},
"11": {
"class_type": "WebhookTransformer",
"inputs": {"field": "prompt", "webhook_context": ["10", 0]}
},
"6": {
"class_type": "CLIPTextEncode",
"inputs": {"text": ["11", 0], "clip": ["4", 1]}
},
"...": "... rest of workflow ...",
"12": {
"class_type": "WebhookSend",
"inputs": {"webhook_context": ["10", 0], "field_1": ["8", 0]}
}
}
Python client (see example_client.py):
import requests, json
# Load image for img2img
image_data = open("input.png", "rb").read()
response = requests.post(
"http://localhost:8188/api/webhook",
files={
"workflow": (None, json.dumps(workflow), "application/json"),
"callback_url": (None, "http://localhost:5001/webhook"),
"prompt": (None, "make it look like a painting"),
"image": ("input.png", image_data, "image/png"),
}
)
Testing
# Terminal 1: Start ComfyUI
python main.py
# Terminal 2: Run example client (image-to-image)
cd custom_nodes/comfy-workflow-api
python example_client.py sample_input.png "turn it into a watercolor painting"
A sample image (sample_input.png) is included in the repo for testing.
License
MIT