Documentation Index Fetch the complete documentation index at: https://dripart-dev-api-v1.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Webhooks push event notifications to your server when jobs complete, inputs are ready, or other events occur. Unlike WebSockets, webhooks don’t require a persistent connection.
Creating a Webhook
from comfy_cloud import ComfyCloudClient
client = ComfyCloudClient( api_key = "comfyui-..." )
with client:
webhook = client.webhooks.create(
url = "https://your-server.com/comfy-webhook" ,
events = [ "job.completed" , "job.failed" ],
)
# Save the secret for signature verification
print ( f "Webhook ID: { webhook.id } " )
print ( f "Secret: { webhook.secret } " ) # Only shown on create!
The webhook secret is only returned when creating the webhook or rotating the secret. Store it securely - you’ll need it to verify signatures.
Event Types
Event Description job.completedJob finished successfully job.failedJob failed with error job.cancelledJob was cancelled job.*All job events input.readyInput file processed input.failedInput upload failed input.*All input events model.readyModel upload complete model.failedModel upload failed model.*All model events archive.readyArchive ZIP ready archive.failedArchive creation failed archive.*All archive events account.low_balanceAccount balance is low *All events
Webhook Payload
{
"event" : "job.completed" ,
"delivery_id" : "dlv_abc123" ,
"data" : {
"id" : "job_xyz789" ,
"status" : "completed" ,
"outputs" : [
{
"id" : "out_123" ,
"type" : "image" ,
"download_url" : "https://storage.comfy.org/..."
}
],
"tags" : [ "my-app" , "batch-1" ],
"created_at" : "2024-01-15T10:00:00Z" ,
"completed_at" : "2024-01-15T10:00:45Z"
}
}
Signature Verification
All webhooks are signed with HMAC-SHA256. Always verify signatures to ensure requests are from ComfyUI Cloud.
Header Description X-Comfy-Signature-256HMAC-SHA256 signature X-Comfy-TimestampUNIX timestamp (seconds)
Python Verification
from comfy_cloud.helpers import verify_webhook_signature, WebhookVerificationError
@app.post ( "/comfy-webhook" )
async def handle_webhook ( request : Request):
payload = await request.body()
signature = request.headers.get( "X-Comfy-Signature-256" )
timestamp = request.headers.get( "X-Comfy-Timestamp" )
try :
verify_webhook_signature(
payload = payload,
signature = signature,
timestamp = timestamp,
secret = WEBHOOK_SECRET ,
)
except WebhookVerificationError as e:
raise HTTPException( 400 , str (e))
# Process the webhook
data = await request.json()
if data[ "event" ] == "job.completed" :
job_id = data[ "data" ][ "id" ]
outputs = data[ "data" ][ "outputs" ]
# Handle completed job...
return { "status" : "ok" }
Parse and Verify Together
from comfy_cloud.helpers import parse_webhook
@app.post ( "/comfy-webhook" )
async def handle_webhook ( request : Request):
payload = await request.body()
webhook = parse_webhook(
payload = payload,
signature = request.headers.get( "X-Comfy-Signature-256" ),
timestamp = request.headers.get( "X-Comfy-Timestamp" ),
secret = WEBHOOK_SECRET ,
)
print ( f "Event: { webhook.event } " )
print ( f "Data: { webhook.data } " )
print ( f "Timestamp: { webhook.timestamp } " )
return { "status" : "ok" }
Manual Verification
The signature is computed as:
HMAC-SHA256(secret, timestamp + "." + body)
Example in Python without the SDK:
import hmac
import hashlib
import time
def verify_signature ( payload : bytes , signature : str , timestamp : str , secret : str ):
# Check timestamp is recent (prevent replay attacks)
ts = int (timestamp)
if abs (time.time() - ts) > 300 : # 5 minute tolerance
raise ValueError ( "Timestamp too old" )
# Compute expected signature
signed_payload = f " { timestamp } ." .encode() + payload
expected = hmac.new(
secret.encode(),
signed_payload,
hashlib.sha256,
).hexdigest()
# Constant-time comparison
if not hmac.compare_digest(signature, expected):
raise ValueError ( "Invalid signature" )
Managing Webhooks
List Webhooks
webhooks = client.webhooks.list()
for wh in webhooks.webhooks:
print ( f " { wh.id } : { wh.url } -> { wh.events } " )
Rotate Secret
If your secret is compromised:
webhook = client.webhooks.rotate_secret(webhook_id)
new_secret = webhook.secret # Update your server with this
Delete Webhook
client.webhooks.delete(webhook_id)
Retry Policy
Failed deliveries are retried with exponential backoff:
Attempt Delay 1 Immediate 2 1 minute 3 5 minutes 4 30 minutes 5 2 hours
Webhooks are considered failed if:
Your server returns a non-2xx status code
Connection timeout (30 seconds)
DNS resolution fails
Best Practices
Never trust webhook payloads without verifying the signature. This prevents attackers from spoofing events.
Return a 2xx response within 30 seconds. Do heavy processing asynchronously after acknowledging receipt.
Use delivery_id to deduplicate. Retries may cause the same event to be delivered multiple times.
Always use HTTPS endpoints. HTTP webhooks are rejected in production.
Per-Job Webhooks
You can also specify a webhook URL when creating a job:
job = client.jobs.create(
workflow = { ... },
webhook_url = "https://your-server.com/job-webhook" ,
)
This webhook receives events only for that specific job, using your account’s default webhook secret.