Documentation Index Fetch the complete documentation index at: https://www.edenai.co/docs/llms.txt
Use this file to discover all available pages before exploring further.
Webhooks let you receive results from async Universal AI jobs via HTTP callbacks instead of polling. When a job completes, Eden AI sends a signed POST request to the URL you provided.
How It Works
Submit an async request with a webhook_receiver in the payload.
Eden AI processes the job in the background.
On completion, Eden AI sends a signed POST request to your webhook_receiver with the result.
Request Parameters
Field Type Required Description webhook_receiverstring (URL)no HTTPS URL that will receive the POST when the job completes or fails. Validated against SSRF. user_webhook_parametersobjectno Free-form JSON object echoed back as user_parameters in the webhook payload. Useful for correlating the callback to your own records.
Every webhook request Eden AI sends includes these headers:
Header Example value Description Content-Typeapplication/jsonBody is always JSON. User-AgentEdenAI/Ai-FeaturesIdentifies the sender. X-Edenai-WebhooktrueFlag marking the request as an Eden AI webhook. X-Edenai-Signaturea8f3... (hex)RSA PKCS1 v1.5 signature of the payload. X-Edenai-Hash-AlgorithmSHA256Hash algorithm used to produce the signature.
Webhook Payload
{
"event" : "async_job_completed" ,
"job_id" : "550e8400-e29b-41d4-a716-446655440000" ,
"status" : "success" ,
"feature" : "ocr" ,
"subfeature" : "ocr_async" ,
"provider" : "amazon" ,
"model" : null ,
"created_at" : "2026-04-21T12:00:00+00:00" ,
"finished_at" : "2026-04-21T12:01:30+00:00" ,
"output" : { "raw_text" : "..." },
"user_parameters" : { "internal_ref" : "order-42" }
}
Field Type Description eventstringAlways "async_job_completed". job_idstring (UUID)ID of the completed job. status"success" | "fail"Final job status. featurestringFeature name (e.g. "ocr", "audio"). subfeaturestringSubfeature name (e.g. "ocr_async"). providerstringProvider that produced the result. modelstring | nullProvider-specific model, if any. created_atstring (ISO 8601)When the job was created. finished_atstring (ISO 8601)When the job finished. outputobjectNormalized result. Present only on status: "success". errorobject{ "message": "..." }. Present only on status: "fail".original_responseanyRaw provider response. Only present when the request had show_original_response: true. user_parametersobjectEcho of user_webhook_parameters. Absent if not set.
Signature Verification
Every webhook is signed with Eden AI’s RSA private key so you can verify it was not forged or tampered with in transit.
Ask Eden AI support for the webhook public key (webhook_rsa.pub.pem) and store it with your service configuration.
The signature is built as follows:
The payload is serialized with canonical JSON (sorted keys, 2-space indent, UTF-8).
sha256(canonical_json_bytes) is computed and its hex digest is taken.
That hex digest is signed with RSA PKCS1 v1.5 / SHA-256.
The hex-encoded signature is sent in X-Edenai-Signature.
To verify, reproduce the same canonical JSON before hashing — do not re-serialize with default formatting.
Example
Dependencies: pip install requests flask pycryptodome orjson Sending the request: import requests
url = "https://api.edenai.run/v3/universal-ai/async"
headers = {
"Authorization" : "Bearer YOUR_API_KEY" ,
"Content-Type" : "application/json" ,
}
payload = {
"model" : "ocr/ocr_async/amazon" ,
"input" : { "file" : "YOUR_FILE_UUID_OR_URL" },
"webhook_receiver" : "https://your-server.com/webhooks/edenai" ,
"user_webhook_parameters" : { "internal_ref" : "order-42" },
}
response = requests.post(url, headers = headers, json = payload)
print (response.json()) # Contains the job ID
Receiving and verifying the webhook (Flask): import hashlib
import orjson
from flask import Flask, request, abort, jsonify
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
app = Flask( __name__ )
with open ( "edenai_webhook_rsa.pub.pem" , "rb" ) as f:
EDENAI_PUBLIC_KEY = RSA .import_key(f.read())
def verify_signature ( raw_body : bytes , signature_hex : str ) -> bool :
payload = orjson.loads(raw_body)
canonical = orjson.dumps(
payload,
option = orjson. OPT_INDENT_2 | orjson. OPT_SORT_KEYS ,
)
hash_hex = hashlib.sha256(canonical).hexdigest()
verifier = PKCS1_v1_5 .new( EDENAI_PUBLIC_KEY )
digest = SHA256 .new( data = hash_hex.encode( "utf-8" ))
try :
return verifier.verify(digest, bytes .fromhex(signature_hex))
except ValueError :
return False
@app.route ( "/webhooks/edenai" , methods = [ "POST" ])
def handle_webhook ():
signature = request.headers.get( "X-Edenai-Signature" , "" )
if not verify_signature(request.get_data(), signature):
abort( 401 , description = "Invalid Eden AI webhook signature" )
payload = request.get_json()
if payload[ "status" ] == "success" :
print ( f "Job { payload[ 'job_id' ] } completed: { payload[ 'output' ] } " )
else :
print ( f "Job { payload[ 'job_id' ] } failed: { payload[ 'error' ][ 'message' ] } " )
return jsonify({ "received" : True }), 200
Dependencies: npm install express safe-stable-stringify Sending the request: ( async () => {
const url = "https://api.edenai.run/v3/universal-ai/async" ;
const headers = {
Authorization: "Bearer YOUR_API_KEY" ,
"Content-Type" : "application/json" ,
};
const payload = {
model: "ocr/ocr_async/amazon" ,
input: { file: "YOUR_FILE_UUID_OR_URL" },
webhook_receiver: "https://your-server.com/webhooks/edenai" ,
user_webhook_parameters: { internal_ref: "order-42" },
};
const response = await fetch ( url , {
method: "POST" ,
headers ,
body: JSON . stringify ( payload ),
});
console . log ( await response . json ()); // Contains the job ID
})();
Receiving and verifying the webhook (Express): const express = require ( "express" );
const crypto = require ( "crypto" );
const stringify = require ( "safe-stable-stringify" );
const fs = require ( "fs" );
const app = express ();
const EDENAI_PUBLIC_KEY = fs . readFileSync ( "edenai_webhook_rsa.pub.pem" , "utf8" );
function verifySignature ( rawBody , signatureHex ) {
const payload = JSON . parse ( rawBody . toString ( "utf8" ));
const canonical = stringify ( payload , null , 2 ); // sorted keys + 2-space indent
const hashHex = crypto . createHash ( "sha256" ). update ( canonical , "utf8" ). digest ( "hex" );
const verifier = crypto . createVerify ( "RSA-SHA256" );
verifier . update ( hashHex , "utf8" );
verifier . end ();
try {
return verifier . verify ( EDENAI_PUBLIC_KEY , Buffer . from ( signatureHex , "hex" ));
} catch {
return false ;
}
}
app . use ( "/webhooks/edenai" , express . raw ({ type: "application/json" }));
app . post ( "/webhooks/edenai" , ( req , res ) => {
const signature = req . header ( "X-Edenai-Signature" ) || "" ;
if ( ! verifySignature ( req . body , signature )) {
return res . status ( 401 ). send ( "Invalid Eden AI webhook signature" );
}
const payload = JSON . parse ( req . body . toString ( "utf8" ));
if ( payload . status === "success" ) {
console . log ( `Job ${ payload . job_id } completed:` , payload . output );
} else {
console . log ( `Job ${ payload . job_id } failed: ${ payload . error . message } ` );
}
res . status ( 200 ). json ({ received: true });
});
app . listen ( 3000 );
Do not re-serialize the parsed JSON with defaults (e.g. JSON.stringify(obj) or json.dumps(obj) without sort_keys=True, indent=2) — any difference in spacing, key ordering, or unicode escaping will make the signature fail to verify.
Retry Behavior
Eden AI retries webhook delivery up to 3 times with exponential backoff between attempts (max delay 30 s) when the receiver returns a transient failure. Each attempt has a per-request timeout of 30 s.
Outcome Retried? HTTP 2xx — HTTP 4xx No HTTP 5xx Yes Per-request timeout (30 s) Yes Connection / network error Yes
Keep your webhook handler idempotent — Eden AI may deliver the same job_id more than once if a retry races with a slow response from your server.
Webhook vs Polling
Webhooks Polling How it works Eden AI pushes the result to your URL You repeatedly call GET /v3/universal-ai/async/{job_id} Latency Immediate notification on completion Depends on polling interval Efficiency No wasted requests Requires repeated API calls Setup Requires a publicly accessible endpoint Works from any client
Next Steps
Monitoring Track async job results and API usage in the dashboard