# Bulk HTTP Request & Webhook Sender \[$0.80/1K 💰] (`cybermax/bulk-http-requests`) Actor

💰 $0.80 per 1,000 requests answered; no-response requests free, no start fee. Bulk GET, POST, PUT, PATCH or DELETE to any API or webhook, from a URL list or one request per dataset row with {{field}} templates. Retries, rate limits, JSON parsing. Cheaper than memo23's HTTP request ($5/1K).

- **URL**: https://apify.com/cybermax/bulk-http-requests.md
- **Developed by:** [CyberMax](https://apify.com/cybermax) (community)
- **Categories:** Developer tools, Automation, MCP servers
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.80 / 1,000 request answereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Hookvolley: Bulk HTTP Requests, API Caller & Webhook Sender

#### Call any API or webhook thousands of times, from a list or one request per dataset row

Hookvolley sends HTTP requests in bulk (**GET, POST, PUT, PATCH, DELETE, HEAD**) and saves every response: status, timing, parsed JSON or text, and the fields you want pulled out. Build requests from a URL list, a JSON list, or **one request per row of a dataset** with `{{field}}` templates, or post rows in batches to a **webhook**. Retries with backoff, `Retry-After`, per-host rate limits, redirects and timeouts are built in. **$0.80 per 1,000 requests, no start fee.**

**[▶ Try it now with a prefilled input](https://apify.com/cybermax/bulk-http-requests)** · $0.0008 per request answered (1,000 calls = $0.80) · requests that never get a response are free · Apify's free plan includes $5 of credit a month

**Built for:** automation builders who need a reliable "HTTP request" step in Apify workflows · developers calling an API for a list of IDs, domains or SKUs · teams pushing scraped rows into a CRM, Slack, Airtable or their own backend · API health and change monitoring · AI agents that need a safe HTTP tool

**Real output** (real run on Apify, 10 Nov 2026: 5 requests in 1.9 s at 256 MB, platform cost $0.00008):

| method | url | status | ms | extracted |
|---|---|---|---|---|
| GET | https://jsonplaceholder.typicode.com/posts/1 | 200 | 31 | `title`: "sunt aut facere repellat provident…" |
| GET | https://jsonplaceholder.typicode.com/users/1 | 200 | 11 | `name`: "Leanne Graham" |
| POST | https://postman-echo.com/post (body `{"hello": "world"}`) | 200 | 64 | `json.hello`: "world" |
| GET | https://jsonplaceholder.typicode.com/nope | 404 | 10 | |
| GET | http://169.254.169.254/latest/meta-data/ | blocked | | private/internal addresses are refused (not charged) |

A second run posted a 25-row dataset to a webhook in 3 batches of up to 10 rows (3 requests, all 200, 3.1 s).

```json
{"urls": ["https://jsonplaceholder.typicode.com/posts/1", "https://jsonplaceholder.typicode.com/users/1"], "extractFields": ["title", "name"]}
```

### Three ways to build requests

| Way | Input |
|---|---|
| **A list of URLs** (same method, headers, body) | `{"urls": ["https://api.example.com/a", "https://api.example.com/b"], "headers": {"Authorization": "Bearer YOUR_KEY"}}` |
| **Individual requests** | `{"requests": [{"url": "https://api.example.com/orders", "method": "POST", "body": {"sku": "A1", "qty": 2}, "label": "order-1"}]}` |
| **One request per dataset row** | `{"datasetId": "…", "urlTemplate": "https://api.example.com/companies/{{domain\|url}}"}` |
| **Rows to a webhook in batches** | `{"datasetId": "…", "method": "POST", "urlTemplate": "https://hooks.example.com/abc", "batchSize": 100}` |

Templates: `{{field}}`, `{{nested.field}}`, `{{field|url}}` (URL-encoded), `{{field|json}}` (JSON-encoded) and `{{json}}` (the whole row). With POST/PUT/PATCH and no body template, the row (or the batch as a JSON array) is the body.

### What you get for each request

| Field | What it is |
|---|---|
| `label`, `method`, `url`, `finalUrl` | The request (and where redirects ended). |
| `status`, `ok`, `statusText` | HTTP status; `ok` is true for 2xx. |
| `body` | Parsed JSON, or text; cut at `maxResponseKb` (`truncated: true`). |
| `extracted` | Values of `extractFields` (dotted paths such as `data.price` or `items.0.id`). |
| `headers` | Response headers (when `includeResponseHeaders` is on). |
| `durationMs`, `totalMs`, `attempts` | Last attempt's time, total time with retries, and how many attempts it took. |
| `inputRow` | The dataset row the request came from (when `includeInputRow` is on). |
| `change` | `new` or `changed` in monitor mode. |
| `error` | Why there was no response (DNS, timeout, blocked address, invalid URL). |

### Reliability and limits

- **Retries:** network errors, timeouts, 408, 425, 429 and 5xx are retried (`maxRetries`, default 3) with exponential backoff, honouring `Retry-After`.
- **Rate limits:** `maxRequestsPerSecondPerHost` (default 5) and `maxConcurrency` (default 5, up to 20) keep you inside an API's limits.
- **Safety:** only http and https; private, loopback, link-local and cloud-metadata addresses are refused, also after redirects.
- Up to 100,000 requests per run.

### Run it on a schedule (API and price monitoring)

1. Turn on `"monitor": true` with a `monitorKey`, save as a **task**.
2. **Schedules → Create**: hourly or daily.
3. Each run returns only requests whose status or body changed; unchanged responses are free. Add an email, Slack or webhook integration for alerts.

### Integrations

- **Chain after any scraper:** in the scraper's **Integrations** add "Run Actor" → `cybermax/bulk-http-requests` with `{"datasetId": "{{resource.defaultDatasetId}}", "method": "POST", "urlTemplate": "https://your-webhook", "batchSize": 50}`.
- **API (curl):**
  ```bash
  curl -X POST "https://api.apify.com/v2/acts/cybermax~bulk-http-requests/run-sync-get-dataset-items?token=YOUR_TOKEN" \
    -H "Content-Type: application/json" -d '{"urls": ["https://jsonplaceholder.typicode.com/posts/1"]}'
  ```
- **Python:** `ApifyClient(token).actor("cybermax/bulk-http-requests").call(run_input={"urls": [...]})`
- **Make, Zapier, n8n:** use it when a workflow needs thousands of calls with retries and rate limits that a single HTTP module can't handle.
- **AI agents (MCP):** `https://mcp.apify.com?actors=cybermax/bulk-http-requests`.

### Why Hookvolley

| | Hookvolley | Typical HTTP request Actor on the Store |
|---|---|---|
| One request per dataset row, batches to webhooks | Yes, with templates | Often a plain URL list |
| Retries, Retry-After, per-host rate limit | Built in | Often a fixed retry or none |
| JSON field extraction | Yes | Rare |
| Monitor mode | Changed responses only, unchanged = free | Rare |
| Price | **$0.80 per 1,000 requests, no start fee** | $0.92–$5 per 1,000 requests (Store prices on 10 Nov 2026) |

### FAQ

**Is a 404 or 500 charged?** Yes: the request was sent and the API answered (after retries for 5xx). Requests that never get a response, invalid inputs and blocked addresses are free.

**Where do API keys go?** In `headers` (e.g. `Authorization`). They stay in your run's input on your own Apify account.

**Can it scrape websites?** It sends exactly the requests you define and returns the raw response; for structured website data use a dedicated scraper. Use it only with APIs and sites you are allowed to call, within their terms and rate limits.

**How big can responses be?** Bodies are saved up to `maxResponseKb` (512 KB by default, 9 MB max). Use `responseFormat: "none"` when you only need status codes.

### Also from CyberMax

- [Rowmeld: Merge & Deduplicate Datasets](https://apify.com/cybermax/merge-dedupe-datasets) · clean the rows before you send them.
- [Bouncefern: URL Status Checker](https://apify.com/cybermax/url-status-checker) · status codes and redirects for any list of URLs.
- [Pageshuck: PDF to Text & Markdown](https://apify.com/cybermax/pdf-to-text) · documents to text for your pipeline.

Found this useful? A quick rating helps others find it.

© 2026 CyberMax. All rights reserved.

# Actor input Schema

## `urls` (type: `array`):

One request per URL, using the method, headers and body below.

## `method` (type: `string`):

HTTP method for the URLs and dataset requests (individual requests can set their own).

## `headers` (type: `object`):

Sent with every request, e.g. {"Authorization": "Bearer ...", "Accept": "application/json"}. {{field}} placeholders work with a dataset.

## `body` (type: `string`):

Request body for POST, PUT and PATCH (JSON or text). Set a Content-Type header for non-JSON bodies.

## `requests` (type: `array`):

Full control per request: \[{"url": "...", "method": "POST", "headers": {...}, "body": {...}, "label": "order-1"}]. Object bodies are sent as JSON.

## `datasetId` (type: `string`):

Optional. Each row (or batch of rows) becomes a request built from the templates below.

## `urlTemplate` (type: `string`):

e.g. https://api.example.com/companies/{{domain|url}} or your webhook URL. {{field}}, {{nested.field}}, {{field|url}}, {{json}}.

## `bodyTemplate` (type: `string`):

Optional, e.g. {"name": {{name|json}}, "site": {{url|json}}}. Empty with POST/PUT/PATCH = the row (or batch) as JSON.

## `batchSize` (type: `integer`):

Above 1, rows are sent in batches: the body is a JSON array of rows (templates see {{items}} and {{count}}).

## `maxConcurrency` (type: `integer`):

How many requests run at the same time.

## `maxRequestsPerSecondPerHost` (type: `integer`):

Keeps you within the API's rate limit.

## `timeoutSecs` (type: `integer`):

A request that takes longer is retried, then reported as failed.

## `maxRetries` (type: `integer`):

Retries network errors, timeouts, 408, 425, 429 and 5xx with exponential backoff, honouring Retry-After.

## `followRedirects` (type: `boolean`):

Follow 3xx redirects (up to 5; each hop is checked for safety).

## `responseFormat` (type: `string`):

How the response body is saved in each row.

## `maxResponseKb` (type: `integer`):

Longer bodies are cut and marked truncated.

## `extractFields` (type: `array`):

Optional dotted paths, e.g. data.price or items.0.id, returned in an extracted object.

## `includeResponseHeaders` (type: `boolean`):

Adds the response headers to each row.

## `includeInputRow` (type: `boolean`):

Adds the dataset row a request was built from.

## `monitor` (type: `boolean`):

Returns a request only when its status or body changed since the last monitor run. Unchanged responses are not charged.

## `monitorKey` (type: `string`):

Optional name for this watchlist's saved state.

## Actor input object example

```json
{
  "urls": [
    "https://jsonplaceholder.typicode.com/posts/1",
    "https://jsonplaceholder.typicode.com/users/1"
  ],
  "method": "GET",
  "headers": {},
  "batchSize": 1,
  "maxConcurrency": 5,
  "maxRequestsPerSecondPerHost": 5,
  "timeoutSecs": 30,
  "maxRetries": 3,
  "followRedirects": true,
  "responseFormat": "auto",
  "maxResponseKb": 512,
  "includeResponseHeaders": false,
  "includeInputRow": false,
  "monitor": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://jsonplaceholder.typicode.com/posts/1",
        "https://jsonplaceholder.typicode.com/users/1"
    ],
    "headers": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("cybermax/bulk-http-requests").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        "https://jsonplaceholder.typicode.com/posts/1",
        "https://jsonplaceholder.typicode.com/users/1",
    ],
    "headers": {},
}

# Run the Actor and wait for it to finish
run = client.actor("cybermax/bulk-http-requests").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://jsonplaceholder.typicode.com/posts/1",
    "https://jsonplaceholder.typicode.com/users/1"
  ],
  "headers": {}
}' |
apify call cybermax/bulk-http-requests --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cybermax/bulk-http-requests"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/M3m6Dor6lWIg18a5U/builds/WSjf1SOVh2cKzRgfJ/openapi.json
