Note: Webhooks are currently supported in beta.
Overview
Webhooks allow you to receive real-time notifications when your Parallel Task Runs complete, eliminating the need for constant polling—especially for long-running or research-intensive tasks.
Our webhooks follow standard webhook conventions to ensure security and interoperability.
Once webhooks are enabled for your account, you can securely send notifications to any URL of your choice.
Setup
Email support@parallel.ai to enable webhooks for your account.
2. Receive Your Account Secret
After emailing, you’ll receive a unique account secret via email.
This will be the default secret used if you don’t provide one in your requests.
Add a webhook
object to your task run request:
curl --request POST \
--url https://api.parallel.ai/v1beta/tasks/runs \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"task_spec": {
"output_schema": "Find the GDP of the specified country and year"
},
"input": "France (2023)",
"processor": "core",
"metadata": {
"key": "value"
},
"webhook": {
"url": "https://your-domain.com/webhooks/parallel",
"event_types": ["task_run.status"],
"secret": "your-custom-secret"
}
}'
Webhook Parameters
Parameter | Type | Required | Description |
---|
url | string | Yes | Your webhook endpoint URL (any domain allowed) |
event_types | array | Yes | Currently only supports ["task_run.status"] |
secret | string | No | Optional custom secret. If not provided, uses your account secret |
Webhook Events
Task Run Status Events
We currently support webhook notifications for task run status changes using the event type task_run.status
. When a task run completes (successfully or with failure), we’ll send a POST request to your webhook URL.
Your webhook endpoint will receive requests with these headers:
{
"Content-Type": "application/json",
"webhook-id": "whevent_abc123def456",
"webhook-timestamp": "1751498975",
"webhook-signature": "v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4="
}
Header Details:
webhook-id
: Unique identifier for each webhook event
webhook-timestamp
: Unix timestamp in seconds
webhook-signature
: Versioned signature, e.g. v1,<base64 signature>
Request Body
Successful Task Completion
{
"timestamp": "2025-04-23T20:21:48.037943Z",
"type": "task_run.status",
"data": {
"run_id": "trun_9907962f83aa4d9d98fd7f4bf745d654",
"status": "completed",
"is_active": false,
"warnings": null,
"error": null,
"processor": "core",
"metadata": {
"key": "value"
}
}
}
Failed Task
{
"timestamp": "2025-04-23T20:21:48.037943Z",
"type": "task_run.status",
"data": {
"run_id": "trun_9907962f83aa4d9d98fd7f4bf745d654",
"status": "failed",
"is_active": false,
"warnings": null,
"error": {
"message": "Task execution failed",
"details": "Additional error details"
},
"processor": "core",
"metadata": {
"key": "value"
}
}
}
Security & Reliability
HMAC Signature Verification
Webhook requests are signed using HMAC-SHA256. The signature header is formatted as v1,<base64 signature>
. The signature is computed over the string:
<webhook-id>.<webhook-timestamp>.<payload>
Where:
<webhook-id>
: The value of the webhook-id
header
<webhook-timestamp>
: The value of the webhook-timestamp
header
<payload>
: The exact (minified) JSON body of the webhook request
You must parse the version and the signature before verifying.
Here’s how to verify the signature:
#!/bin/bash
# Get the signature and metadata from the webhook headers
HEADER_SIGNATURE="$WEBHOOK_SIGNATURE_HEADER" # e.g. v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
WEBHOOK_ID="$WEBHOOK_ID_HEADER" # e.g. msg_2KWPBgLlAfxdpx2AI54pPJ85f4W
WEBHOOK_TIMESTAMP="$WEBHOOK_TIMESTAMP_HEADER" # e.g. 1674087231
# Parse version and signature
VERSION=$(echo "$HEADER_SIGNATURE" | cut -d',' -f1)
RECEIVED_SIGNATURE=$(echo "$HEADER_SIGNATURE" | cut -d',' -f2)
# Read the minified JSON payload from stdin
PAYLOAD=$(cat /dev/stdin)
# Concatenate for signing: <id>.<timestamp>.<payload>
TO_SIGN="$WEBHOOK_ID.$WEBHOOK_TIMESTAMP.$PAYLOAD"
SECRET="your-secret-key"
GENERATED_SIGNATURE=$(echo -n "$TO_SIGN" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)
# Compare signatures
if [ "$GENERATED_SIGNATURE" = "$RECEIVED_SIGNATURE" ]; then
echo "✅ Signature verification successful (version: $VERSION)"
else
echo "❌ Signature verification failed"
echo "Generated: $GENERATED_SIGNATURE"
echo "Received: $RECEIVED_SIGNATURE"
exit 1
fi
Retry Policy
We implement a robust retry policy to ensure webhook delivery:
- Maximum retries: Multiple attempts over ~24 hours.
- Initial delay: 5 seconds
- Backoff strategy: Exponential backoff
If a webhook fails to deliver after all retry attempts, we’ll stop sending further notifications for that event.
Best Practices
1. Always Return 2xx Status
Your webhook endpoint should return a 2xx HTTP status code to acknowledge receipt. Any other status code will trigger retries.
2. Verify Signatures
Always verify HMAC signatures using your account secret or custom secret to ensure webhook authenticity. Ensure that you are calculating signatures using the proper process as shown above.
3. Handle Duplicates
Webhook events may be delivered multiple times due to retries. Implement idempotency in your webhook handler.
4. Process Asynchronously
Process webhook events asynchronously to avoid timeouts and ensure quick response times.
5. Log Events
Maintain logs of received webhook events for debugging and monitoring purposes.
Example Webhook Handler
Here’s a simple example of a webhook handler in Python:
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = "your-secret-key"
def verify_webhook_signature(webhook_id, webhook_timestamp, payload, secret, header_signature):
try:
version, received_signature = header_signature.split(',', 1)
except ValueError:
return False
to_sign = f"{webhook_id}.{webhook_timestamp}.{payload}"
import hmac, hashlib, base64
expected_signature = base64.b64encode(
hmac.new(
secret.encode('utf-8'),
to_sign.encode('utf-8'),
hashlib.sha256
).digest()
).decode('utf-8')
return hmac.compare_digest(expected_signature, received_signature)
@app.route('/webhooks/parallel', methods=['POST'])
def webhook_handler():
# Extract headers
webhook_id = request.headers.get('webhook-id')
webhook_timestamp = request.headers.get('webhook-timestamp')
header_signature = request.headers.get('webhook-signature')
# Get the minified JSON body as string
payload = request.get_data(as_text=True)
# Verify signature
if header_signature and not verify_webhook_signature(webhook_id, webhook_timestamp, payload, WEBHOOK_SECRET, header_signature):
return jsonify({"error": "Invalid signature"}), 401
# Parse the webhook data
data = request.json
# Process the webhook event
if data['type'] == 'task_run.status':
run_id = data['data']['run_id']
status = data['data']['status']
if status == 'completed':
print(f"✅ Task {run_id} completed successfully")
# Handle successful completion
elif status == 'failed':
print(f"❌ Task {run_id} failed")
# Handle failure
# Always return 200 to acknowledge receipt
return jsonify({"status": "received"}), 200
if __name__ == '__main__':
app.run(port=5000)
Responses are generated using AI and may contain mistakes.