Skip to main content

What are Callbacks?

Callbacks are the way Edges delivers results for async and schedule execution modes. Instead of waiting for the entire operation to complete, you receive results progressively as they become available.
When to use callbacks: Choose async or schedule mode when you need to process large datasets, want to consume data in parallel, or need to handle long-running operations without timeouts.
Callbacks are only useful if you can trust that all of them are received and processed.
To ensure you never miss any data, see Why Managing Callback History Matters.
Understanding callback statuses is essential for handling callbacks correctly. Here are quick definitions:
  • PENDING: The callback is queued and waiting to be delivered to your webhook endpoint.
  • RUNNING: The callback has been sent to your endpoint and is being processed (or was successfully delivered).
  • FAILED: The callback delivery failed (e.g., network error, timeout, or your endpoint returned an error status code).
  • SUCCESS: The callback was successfully delivered to your endpoint and your endpoint returned a successful HTTP status code (2xx).
These are callback statuses that indicate the delivery status of the callback itself. They are different from run statuses (like BLOCKED, FAILED, SUCCEEDED) which indicate the execution state of the run. See Understanding Run Statuses vs Callback Statuses below for more details.

Quick Start

1. Set up your webhook endpoint

Create an HTTPS endpoint that can receive POST requests with JSON payloads.

2. Configure your action call

Include a callback parameter with your webhook URL:
{
  "inputs": [...],
  "callback": {
    "url": "https://yourdomain.com/webhook",
    "headers": {
      "Authorization": "Bearer your_token"
    },
    "on": "all"
  }
}
Set "on": "final" instead of "all" if you only want to receive a single callback when the job completes. With "final" mode, the callback indicates the run status, and you then fetch results using GET /runs/{run_uid}/outputs. This is ideal for automation tools like n8n, Make, or Zapier. See Streaming vs Final Callbacks for details.

3. Handle incoming callbacks

  • For on: "all" mode: Process the JSON payloads as they arrive. Each callback contains results and metadata. Verify completeness by comparing the run output count with received results.
  • For on: "final" mode: Receive a single callback with run status, then fetch all results using GET /runs/{run_uid}/outputs.
For detailed setup and management, see the sections below.

Understanding Async and Schedule Modes

async and schedule execution modes are both async modes: the results are not sent immediately in the response payload but delivered during execution via callbacks.
schedule is built on top of async and only adds ways to postpone and schedule calls. Both modes share the same callback delivery mechanism. For a detailed comparison and guidance on when to use each mode, see When to use live, async or schedule modes..

Setting Up Callbacks

Every action call in async/schedule mode requires a callback parameter:
url
string
Your webhook URL that will receive the callback data. Must be HTTPS.
headers
object
Optional headers for authentication or custom metadata (API keys, etc.).
on
string
default:"all"
Defines when you want to receive callbacks. See Streaming vs Final Callbacks below.
  • "all" (default): Stream callbacks progressively as results are processed
  • "final": Receive a single callback when all inputs are processed or an error occurs
Please refer to each async/schedule action in the API Reference for specific details.

Streaming vs Final Callbacks

By default, Edges sends callbacks progressively as results become available (callback.on: "all"). However, you can opt to receive only a single callback when the entire job completes by setting callback.on: "final".

When to Use Each Mode

Modecallback.onBehaviorBest For
Streaming"all" (default)Multiple callbacks as results are processedReal-time processing, large datasets, progressive data consumption
Final"final"Single callback when job completesAutomation tools (n8n, Make, Zapier), simple integrations, cost optimization

Streaming Mode ("all")

This is the default behavior. You receive callbacks progressively:
  • One callback per batch/page of results with run.status: RUNNING
  • One final callback with run.status: SUCCEEDED when complete
{
  "inputs": [...],
  "callback": {
    "url": "https://yourdomain.com/webhook",
    "on": "all"
  }
}
Advantages:
  • Start processing data immediately as it arrives
  • Better for large datasets (stream instead of waiting)
  • Real-time progress tracking
Considerations:
  • Your endpoint receives multiple requests per run
  • Requires handling multiple callbacks and aggregating results
  • Must verify completeness by comparing output count with received results
Verifying Completeness (All Callbacks Mode): After receiving the final callback with run.status: SUCCEEDED, verify that all callbacks were received:
// 1. Get the run details to retrieve output_count
const runResponse = await fetch(`https://api.edges.run/v1/runs/${run_uid}`, {
  headers: { 'X-API-Key': 'your_api_key' }
});
const run = await runResponse.json();
const expectedOutputCount = run.output_count;

// 2. Count all results received in callbacks
// (You should track this as callbacks arrive)
const receivedResultsCount = /* sum of all results.length from callbacks */;

// 3. Compare counts
if (receivedResultsCount === expectedOutputCount) {
  console.log('✅ All callbacks received - data is complete');
} else {
  console.warn(`⚠️ Missing callbacks: Expected ${expectedOutputCount}, received ${receivedResultsCount}`);
  // Fetch missing results or replay callbacks
  const outputs = await fetch(`https://api.edges.run/v1/runs/${run_uid}/outputs`, {
    headers: { 'X-API-Key': 'your_api_key' }
  });
  // Process missing results
}

Final Mode ("final")

You receive a single callback only when the job is complete:
  • One callback with run.status: SUCCEEDED or run.status: PARTIAL_SUCCEEDED indicating the run status
  • Or one callback with run.status: FAILED if an error occurred
  • Important: The callback indicates the run status but does not contain all results. You must fetch results using the GET /runs/{run_uid}/outputs API endpoint.
{
  "inputs": [...],
  "callback": {
    "url": "https://yourdomain.com/webhook",
    "on": "final"
  }
}
How it works:
  1. Receive final callback with run status (SUCCEEDED, PARTIAL_SUCCEEDED, or FAILED)
  2. Fetch all results using GET /runs/{run_uid}/outputs API endpoint
  3. Process the complete result set
Advantages:
  • Dramatically reduces webhook executions (1 instead of N)
  • Simpler integration logic (no aggregation needed)
  • Lower costs for webhook-based automation platforms
  • Data completeness guaranteed by the GET Results API
Considerations:
  • Must wait for the entire job to complete before receiving status
  • Requires an additional API call to fetch results after the callback
Fetching Results (Final Callback Mode): After receiving the final callback, fetch all results:
// 1. Receive final callback with run.status
// Example callback payload:
// { "run": { "run_uid": "...", "status": "SUCCEEDED" }, ... }

// 2. Fetch all results using the GET API
const outputsResponse = await fetch(
  `https://api.edges.run/v1/runs/${run_uid}/outputs?limit=100&offset=0`,
  { headers: { 'X-API-Key': 'your_api_key' } }
);
const outputs = await outputsResponse.json();

// 3. Process the complete result set
// Note: Use pagination (limit/offset) if there are many results
For automation tools like n8n, Make, or Zapier: Use callback.on: "final" to avoid flooding your workflows with multiple webhook triggers. This can significantly reduce your automation platform costs, especially for jobs with many results.

Data Completeness Guarantees

Each callback mode ensures data completeness differently. Understanding these guarantees helps you build robust integrations that never miss data.

All Callbacks Mode (on: "all")

Results are delivered progressively through multiple callbacks during the run execution. How completeness is ensured:
  1. Each callback contains a subset of the run results
  2. When the run finishes, verify completeness by:
    • Retrieving the run output count using GET /runs/{run_uid} - the output_count field indicates the total number of outputs generated
    • Comparing it with the number of results received via callbacks (count all results from callbacks with run.status: RUNNING and the final run.status: SUCCEEDED)
Outcome: Guarantee: 👉 Data completeness is validated by a final comparison between received callbacks and the run output count.

Final Callback + GET Results Mode (on: "final")

Results are delivered after the run is fully completed using a two-step process. How it works:
  1. A single final callback indicates the run status (SUCCEEDED, PARTIAL_SUCCEEDED, or FAILED)
  2. The client then retrieves all results using the GET /runs/{run_uid}/outputs API call
Outcome:
  • Results are fetched directly from the server via the API
  • No intermediate callbacks are involved
  • No callback verification needed
Guarantee: 👉 Data completeness is ensured by the GET Results API, not by callbacks.

Summary

ModeResult DeliveryCompleteness Check
All Callbacks (on: "all")Multiple callbacks during executionFinal comparison with run output count
Final Callback + GET (on: "final")One final callback + API retrievalNo callback verification needed

Which Mode Should I Use?

  • All Callbacks → If you need real-time processing or progressive results
  • Final Callback + GET → If you prefer a simpler flow and can wait for completion

Callback Structure

All callbacks follow the same consistent format:
Callback Delivery: Both async and schedule modes deliver results via the callback URL provided in your request.Consistent Format: All execution modes use the same action logic, so inputs and results are identical regardless of mode.Error Handling: Errors follow the standard API error format.
async callback format
{
  "callback_ref_uid": "string",
  "run": {
    "run_uid": "string",
    "batch_uid": "string",
    "status": "CREATED" || "INVALID" || "QUEUED" || "SCHEDULED" || "BLOCKED" || "STOPPED" || "RUNNING" || "FAILED" || "PARTIAL_SUCCEEDED" || "SUCCEEDED",
  },
  "input": {} || null,
  "custom_data": {} || null,
  "error": {} || null,
  "results": [] || null
}
To track or tag your inputs, you can attach any custom metadata to each input via the custom_data field. It is especially useful in async and schedule modes to help you get context on the results. This applies even when running with multiple inputs: each input will produce one or more callbacks, each carrying its own custom_data.This object is then injected as-is at the root of every callback, allowing you to correlate each result with your own data or internal references.

Callback Payload Fields

callback_ref_uid
string
Stable callback identifier that remains the same across retries. Use this for safe deduplication — even if your endpoint receives multiple retries, this identifier always refers to the same logical callback event.
run
object
Execution context containing run_uid (unique to the entire run), batch_uid (unique to this batch), and status (current execution state).
input
object | null
The processed input data that was used for this batch (cleaned and validated by our engine).
custom_data
object | null
Your custom data passed via inputs.custom_data. Available at the root level for easy access.
error
object | null
Error details if this batch failed. Follows the standard API error format.
results
array | null
The actual results for this batch (null if there was an error). Format matches the specific action’s output.

Callback HTTP Headers

Webhook requests include the following HTTP headers for identification and tracking:
HeaderDescription
X-Callback-RefStable callback identifier, identical across retries (same as callback_ref_uid in the payload). Use for deduplication.
X-Run-CallbackUnique identifier for each delivery attempt. Changes on every retry. Use for delivery tracking.
Use X-Callback-Ref (or callback_ref_uid in the payload) to detect and safely ignore duplicate callbacks. Use X-Run-Callback to log and debug individual delivery attempts.

Understanding Run Statuses vs Callback Statuses

It’s important to distinguish between two different types of statuses in the callback system:

Run Statuses

Run statuses (found in run.status within the callback payload) indicate the execution state of the entire run or batch. The available run statuses are:
  • CREATED: The run or schedule has been created but not yet queued for execution.
  • INVALID: The run or schedule is invalid (e.g., due to bad input or configuration).
  • QUEUED: The run is waiting in the queue to be executed.
  • SCHEDULED: The run is scheduled to execute at a future time (applies to scheduled/CRON jobs).
  • BLOCKED: The run is blocked because the input is invalid (bad input). For example, certain URLs like https://www.linkedin.com/products/... do not correspond to a valid LinkedIn company page. The callback itself is processed correctly, but the input cannot be processed.
  • STOPPED: The run was stopped before completion (manually or by the system).
  • RUNNING: The run is currently in progress.
  • FAILED: The run has failed. This can occur when the input seems correct, but during processing in the callback, it returns a failed status with an error (e.g., 424 – No results). In this case, the callback itself was processed correctly, even if the page doesn’t exist or returns nothing.
  • PARTIAL_SUCCEEDED: The run completed with some errors, but partial results are available.
  • SUCCEEDED: The run completed successfully.

Callback Statuses

Callback statuses (used when filtering callbacks via the API) indicate the delivery status of the callback to your webhook endpoint:
  • PENDING: The callback is queued and waiting to be delivered.
  • RUNNING: The callback has been sent to your endpoint.
  • FAILED: The callback delivery failed (network error, timeout, or your endpoint returned an error).
  • SUCCESS: The callback was successfully delivered and your endpoint returned a successful response.

Understanding BLOCKED and FAILED Run Statuses

When processing async operations, you may encounter inputs that cannot be processed. Here’s how to interpret the different scenarios:

BLOCKED Status

A run status of BLOCKED indicates that the input is invalid (bad input). This happens when:
  • The input format is incorrect or doesn’t match the expected type
  • The input references a resource that doesn’t exist or isn’t accessible
  • Example: URLs like https://www.linkedin.com/products/... that don’t correspond to a LinkedIn company page
In this case, the callback is processed correctly by Edges, but the input itself cannot be processed because it’s invalid.

FAILED Status (424 – No results)

A run status of FAILED with a 424 – No results error indicates that:
  • The input format appears correct
  • During processing, no results could be found for the provided input
  • The input may redirect to a 404 page on LinkedIn or the target resource doesn’t exist
  • Important: The callback itself was processed correctly by Edges, even though no results were found
Technically, when a callback returns FAILED with 424 – No results, the callback processing itself succeeded — Edges correctly processed your request and determined that no results exist. The failure is in the data retrieval, not in the callback delivery mechanism.

Using custom_data to Track Inputs

One of the most powerful features of callbacks is the ability to attach custom metadata to each input using the custom_data object. This data is sent back in every callback, allowing you to correlate results with your internal systems.
1

1. Add `custom_data` to your inputs

When calling an action in async or schedule mode, include a custom_data field for each input.
{
  "inputs": [
    {
      "linkedin_profile_url": "https://www.linkedin.com/in/elisa-morgera-88b76b95/",
      "custom_data": {
        "internal_id": "12345",
        "type": "lead"
      }
    },
    {
      "linkedin_profile_url": "https://www.linkedin.com/in/astrid-puentes-ria%C3%B1o-83658a14/",
      "custom_data": {
        "internal_id": "671",
        "type": "lead"
      }
    }
  ],
  "callback": {
    "url": "https://yourdomain.com/webhook",
    "headers": {
      "Authorization": "Bearer your_token"
    }
  }
}
2

2. Receive `custom_data` in each callback

Each callback will include the same custom_data at the root level.
Example for the first input:
{
  "callback_ref_uid": "550e8400-e29b-41d4-a716-446655440000",
  "run": { "run_uid": "...", "batch_uid": "...", "status": "RUNNING" },
  "input": {
    "linkedin_profile_url": "https://www.linkedin.com/in/elisa-morgera-88b76b95/"
  },
  "custom_data": {
    "internal_id": "12345",
    "type": "lead"
  },
  "results": [ { "linkedin_profile_id": "...", "full_name": "Elisa Morgera" } ]
}
And for the second input:
{
  "callback_ref_uid": "550e8400-e29b-41d4-a716-446655440001",
  "run": { "run_uid": "...", "batch_uid": "...", "status": "RUNNING" },
  "input": {
    "linkedin_profile_url": "https://www.linkedin.com/in/astrid-puentes-ria%C3%B1o-83658a14/"
  },
  "custom_data": {
    "internal_id": "671",
    "type": "lead"
  },
  "results": [ { "linkedin_profile_id": "...", "full_name": "Astrid Puentes Riaño" } ]
}
3

3. Match and track on your side

You can now correlate each callback to internal data using the custom_data fields.
This makes it easy to process results in your own system — even with multiple inputs and parallel callbacks.

How Callbacks Work

Understanding how callbacks are processed and delivered helps you build robust integrations.

Parallel Processing & Pagination

Async actions are designed to scale efficiently by processing multiple pages concurrently:
  • Improved performance: Pages are processed in parallel
  • Manageable payloads: Each page generates a separate callback
  • Faster time-to-first-result: Start consuming data immediately

Callback Flow

The callback flow depends on the callback.on setting:
1

Trigger Action

Action is triggered in async mode with max_results: 100
2

Auto-Pagination

Backend automatically paginates results with page_size: 10
3

Receive Running Callbacks

You receive 10 callbacks with:
  • run.status: RUNNING (this is the run status, indicating the run is in progress)
  • Each containing ~10 results (may vary slightly as we filter out ads and other content)
4

Final Success Callback

Once the run completes, you receive one final callback with run.status: SUCCEEDED (indicating the run finished successfully)
Important: You may receive multiple callbacks for a single execution, and they may arrive out of order due to parallel processing. Always use the run_uid and batch_uid to track and organize your data.

Handling Callbacks & Idempotency

Even with a single callback (final mode), you should implement idempotency to handle potential retries. For streaming mode, this is even more critical since you receive multiple callbacks.

Best Practices

  • Use callback_ref_uid for deduplication: This stable identifier is the same across retries, making it the recommended way to detect and ignore duplicate callbacks
  • Track processed results using meaningful keys (e.g., linkedin_profile_id) or run_uid/batch_uid
For streaming mode (on: "all"):
  • Aggregate progressively as you receive callbacks with run.status: RUNNING (these contain partial results)
  • Finalize only when you receive a callback with run.status: SUCCEEDED (this indicates the run completed)
  • Verify completeness by comparing the run output_count (from GET /runs/{run_uid}) with the total number of results received in callbacks
  • If counts don’t match, identify and replay missing callbacks or fetch results via the API
For final mode (on: "final"):
  • You receive a single callback indicating the run status (SUCCEEDED, PARTIAL_SUCCEEDED, or FAILED)
  • Fetch all results using GET /runs/{run_uid}/outputs after receiving the callback
  • No aggregation needed - the API returns the complete result set

Benefits

This approach allows you to:
  • Stream results progressively (streaming mode) or receive status then fetch results (final mode)
  • Handle partial failures gracefully
  • Ensure data consistency even with retries
  • Verify data completeness using run output count (streaming mode) or API retrieval (final mode)

Managing Callbacks

Edges provides endpoints to track, retrieve, and manage your callbacks.

Why Managing Callback History Matters

Even with a reliable setup, callbacks may not always reach your endpoint. This is often invisible without using the callback history endpoints — missing callbacks mean missing data.
If you rely solely on your callback URL logs, you might never detect missed callbacks, as they were never delivered to your system.

Common Reasons for Missed Callbacks

  • Network interruptions between Edges and your callback URL
  • Temporary downtime of your server or API endpoint
  • Gateway or firewall restrictions blocking Edges IPs
  • TLS/SSL handshake issues (expired certificates, protocol mismatch)
  • Slow responses from your server causing timeouts
  • Transient cloud provider issues on either side

How to Detect & Resolve Issues

You can use the List Callbacks endpoint to programmatically detect missing or failed callbacks:
  1. Schedule a periodic check (e.g., once per day) to call GET /runs/callbacks filtered by status=FAILED.
For example:
curl --request GET \
  --url 'https://api.edges.run/v1/runs/callbacks?limit=20&sort=-created_at&status=FAILED' \
  --header 'X-API-Key: <your_api_key>'
The number of callbacks can be adjusted with the limit parameter up to 20. You can use the offset param if needed to paginate through results and retrieve all failed callbacks until a date.If you track the run_uid, you can also filter by run_uid to check for specific runs and verify the callbacks were successfully received run by run.
  1. Analyze the http_status field to identify the root cause (e.g., connection refused, timeout).
    If the callback reached your endpoint but ended with an error, inspect your own server logs to diagnose the issue.
http_status is the HTTP status code returned by your endpoint. It is meaningful to identify what’s happening on your callback URL. Refer to the HTTP Status Codes documentation for more details.While it will be enough to identify most issues, you may need to check your own server logs for more details in some cases:
  • 4xx errors: Client-side issues (e.g., authentication, bad request)
  • 5xx errors: Server-side issues (e.g., internal server error,…)
Note that you may also have “silent” failures if you have logic issues in your callback processing code that do not return an error status code but still fail to process the data correctly. So best practice is to implement explicit error handling on your endpoint and always log the received callbacks and their processing results.
  1. Fix the issue(s) (e.g., adjust firewall, fix SSL, improve server response time).
While you can automatically replay failed callbacks, it’s crucial to understand why they failed first. This prevents flooding your endpoint with retries without fixing the underlying issue, ensures you stay within the replay limits, and guarantees you don’t miss any data.Use the replay functionality carefully — you can only replay the same callback up to 3 times.
  1. Replay the affected callbacks with POST /runs/callbacks/{callback_uid}/replay.

Daily Monitoring Example

Recommended Daily Check: Run a cron job that:
  • Fetches all failed callbacks from the past 24 hours
  • Logs the details for investigation
  • Automatically retries transient failures using the replay endpoint
Regularly checking the callback history helps ensure no data is silently lost and allows you to maintain a robust, fault-tolerant integration.

Additional Use Cases for Callback History

  • Post-incident recovery: After downtime, retrieve missed callbacks and replay them to backfill data. You can also use GET /runs/{run_uid}/inputs to recover all input-level data including errors that aren’t available in the outputs endpoint.
  • Audit & compliance: Keep a complete log of all callbacks sent and their statuses for troubleshooting or audits.
  • Performance monitoring: Track the proportion of successful vs failed callbacks over time to improve infrastructure reliability.

Examples

Listing Callbacks

Use GET /runs/callbacks to retrieve all callbacks with filtering and pagination.

Getting a Specific Callback

Use GET /runs/callbacks/{callback_uid} to fetch detailed information:

Replaying Callbacks

Use POST /runs/callbacks/{callback_uid}/replay to retry failed callbacks:
How replay works:
  • Each replay creates a new callback (up to 3 total replays)
  • You can only replay the original callback, not callback responses
  • This helps you track each attempt and identify specific issues with your webhook URL
When to use replay:
  • Your webhook URL was temporarily unavailable
  • You received a callback but want to retry processing
  • You need to debug callback delivery issues
If you have a sandbox, you will have access to several workspaces. The API key will define the current workspace for the calls.

Next Steps

See the API Reference for detailed endpoint usage, parameters, and more examples.