Documentation Index Fetch the complete documentation index at: https://docs.reducto.ai/llms.txt
Use this file to discover all available pages before exploring further.
For production applications, use Svix webhooks instead. Svix provides cryptographic signing, advanced retries, and a debugging dashboard. Direct webhooks are best for prototyping or simple internal integrations.
Direct webhooks send HTTP POST requests directly to your endpoint when jobs complete. Reducto retries failed deliveries up to 3 times with exponential backoff.
Testing with webhook.site
For quick testing, use webhook.site to get a temporary endpoint URL. It shows you the exact payload Reducto sends.
Submitting jobs
Include your webhook URL in the async configuration:
from reducto import Reducto
client = Reducto()
job = client.parse.run_job(
input = "https://example.com/document.pdf" ,
async_ = {
"webhook" : {
"mode" : "direct" ,
"url" : "https://your-app.com/webhook"
},
"metadata" : {
"user_id" : "123" ,
"document_type" : "invoice"
}
}
)
print ( f "Job ID: { job.job_id } " )
Works with all async endpoints: /parse_async, /extract_async, /split_async, /pipeline_async.
Webhook payload
When the job completes, your endpoint receives:
{
"status" : "Completed" ,
"job_id" : "204a39e4-dd10-4c83-a978-0cee4af8cde2" ,
"metadata" : {
"user_id" : "123" ,
"document_type" : "invoice"
}
}
The status is either Completed or Failed. Use job_id to retrieve results with client.job.get().
Handling webhooks
from flask import Flask, request, jsonify
from reducto import Reducto
app = Flask( __name__ )
client = Reducto()
@app.route ( '/webhook' , methods = [ 'POST' ])
def handle_webhook ():
payload = request.json
if payload[ 'status' ] == "Completed" :
job = client.job.get(payload[ 'job_id' ])
# Process job.result
print ( f "Processed job: { payload[ 'job_id' ] } " )
return jsonify({ "received" : True }), 200
Validating requests
Since direct webhooks lack cryptographic signing, validate requests using a secret token in metadata:
import os
from flask import Flask, request, jsonify, abort
from reducto import Reducto
app = Flask( __name__ )
client = Reducto()
WEBHOOK_SECRET = os.environ[ "WEBHOOK_SECRET" ]
# When submitting jobs, include the secret
def submit_job ():
return client.parse.run_job(
input = "https://example.com/document.pdf" ,
async_ = {
"webhook" : { "mode" : "direct" , "url" : "https://your-app.com/webhook" },
"metadata" : { "secret" : WEBHOOK_SECRET , "user_id" : "123" }
}
)
# When handling webhooks, verify the secret
@app.route ( '/webhook' , methods = [ 'POST' ])
def handle_webhook ():
payload = request.json
# Validate secret
if payload.get( 'metadata' , {}).get( 'secret' ) != WEBHOOK_SECRET :
abort( 401 )
if payload[ 'status' ] == "Completed" :
job = client.job.get(payload[ 'job_id' ])
# Process job.result
return jsonify({ "received" : True }), 200
Troubleshooting
Verify your endpoint URL is publicly accessible (not localhost)
Ensure your endpoint returns 2xx status codes
Check your server logs for incoming requests
Webhook received but job retrieval fails
Job IDs expire after 12 hours. Retrieve results promptly after receiving the webhook.
Duplicate webhooks received
Reducto retries failed deliveries. Make your handler idempotent by tracking processed job IDs.
Best practices
Use HTTPS for your webhook endpoint
Validate requests using the token-in-metadata pattern
Return quickly : Return 2xx immediately, process results asynchronously
Be idempotent : Handle duplicate deliveries gracefully
Log everything : Direct webhooks have no dashboard, so log for debugging
For production applications with reliability requirements, use Svix webhooks .