← All sessionsHomeSearch
C7 EST | 14 Day AI Sprint·Day 9 | Office Hours + Advanced Workflows with n8n ( Scraper + Https )·5:03:00

Day 9: Webhook vs API for Good — Apify Scraper, cURL Import, Structured Output, Gmail Drafts, then the Native-Node Rewrite

Akshat Day mentor - ~8 years experience, Head of Digital & AI Innovation Center at a group heard as 'Edoku'; content creator and n8n automation expert (introduced Day 3) · Harshit Office-hours mentor - selling automations, compliance, Ollama sizing, GHL, error handling · Samriddhi Office-hours co-mentor (BaseLabs) - WhatsApp tooling, GHL, Genspark

The short version

  1. Webhook = 'always listening... always waiting for information' - one direction, fires when something happens (Apify says 'run succeeded'). API = the waiter: send a request, the server processes it, a response comes back - two directions. GET gets, POST posts. 'That's it.'
  2. The hard-mode build: Apify scrapes law firms -> webhook pushes only METADATA (a dataset ID) -> HTTP Request pulls the full records (email included) -> AI Agent writes a cold email as strict JSON -> Gmail creates a DRAFT for human review. The webhook tells you something happened; the API is how you go and get it.
  3. 'The most coolest trick': copy the cURL from any API's docs and press Import cURL in the HTTP node - method, headers, URL fill themselves. Then edit two things: the Bearer token (your key, one space after Bearer) and the placeholder in the URL (drag the dataset ID in). Authentication type is whatever the API's docs say - not guessable.
  4. 'I don't trust AI to always follow my instructions': a Structured Output Parser with 'Require Specific Output Format' and a JSON example {subject, email} makes the agent's output reliably parseable; switch Gmail's message type to HTML or the formatting breaks.
  5. SOP: if a native node exists (Gmail, Apify), use it; if not (Fireflies), Webhook/HTTP is the universal connector. Hard mode was taught first ONLY so the concepts stick; production defaults to native nodes.
  6. Office hour: sell a workflow as a JSON template (KBs) or as an agency retainer (host it, connect their APIs, never hand over the JSON); price front ends as credit packs or flat fee with margin ($10 charged, $4 cost); one API key per client. Ollama sizing: an M3 Pro 18 GB runs ~14B models, not DeepSeek 671B; paste your specs into Perplexity and ask.

The concepts

01

Webhook vs API, GET vs POST - the hotel and the waiter

Two booking sites sell the same two rooms. The webhook is how the hotel finds out a room just went - instantly, in one direction.

Forty minutes of analogy before any configuration. Webhook: a listener that never stops waiting; when the event happens (a booking, a completed scrape) it fires once, one way - 'as soon as the booking is done, block those rooms' - carrying a notification, not the goods. API: bidirectional - the waiter takes your request to the kitchen and brings a response back; the hotel date-change example needs confirmation, so it is an API. GET retrieves (the breakfast menu); POST submits (a booking) - quizzed repeatedly until the chat gets it.

Both nodes were already seen: the Webhook in Day 6 (Retell's end-of-call push) and the HTTP node in Day 7 (Fireflies). Today formalizes the pair and adds the SOP: native node if one exists; Webhook/HTTP otherwise.

Why it matters

This is the mental model that makes every 'connect X to n8n' question answerable in ten seconds.

Webhook then API (Day 9): the notification, then the fetch APIFY ACTOR Google Maps scraper "run succeeded" WEBHOOK one-way, POST n8n WEBHOOK node listening (POST) payload = metadata + datasetId n8n HTTP REQUEST Import cURL + Bearer token GET dataset items -> emails AI AGENT -> Gmail DRAFT Structured Output Parser human presses send API = two-way request/response you initiate SOP: native node if it exists (Run an Actor -> Get Dataset Items) | Webhook + HTTP when it does not (Fireflies) GET gets, POST posts - and the method must match on BOTH ends or nothing fires The webhook says something happened; the API is how you go and get it
Day 9's two-node story: the webhook hears that the Apify run finished (metadata only); the HTTP Request then asks the API for the actual dataset.
Go deeper

In one line: Webhook = passive one-way event notification into your workflow; API = two-way request/response you initiate; GET reads, POST writes; prefer native nodes, fall back to Webhook/HTTP.

Webhook 'always listening', single-direction, event-fired (l3186054 0:24)

API bidirectional: send, process on server, response returns (l3186054 0:43)

GET = retrieve; POST = submit/create (l3186054 1:12)

Seen before: Webhook in the voice session, HTTP in the Jerry/Fireflies session (l3186054 0:01-0:03)

SOP: native node when available; Webhook/HTTP when not (l3186054 1:50)

▶ Watch this taught:

02

Apify actor -> n8n webhook: the payload is a dataset ID, not the data

how-to

The scrape finishes, the webhook fires - and the payload has no lawyers in it. Just an ID. That is by design.

Use case: cold outreach to New York law firms (heavy paper users) needs a list; Google is manual, so scrape. On Apify search 'Google Maps email', pick the top actor (54,000 ratings, 4.4 stars); set search term, location, and 'number of places to extract' (1-2 for the demo; scales to 'hundred thousand'); Save and Start. Runs take 30-40 s and vary. In the actor's Integrations tab add an HTTP Webhook (generic, black icon), method POST, URL = the n8n Webhook node's TEST URL, condition 'Start when run succeeded.' In n8n set the Webhook node to POST (Apify is sending), click 'Listen for test event' BEFORE re-running the actor. The payload that arrives is run metadata including defaultDatasetId - the contacts live behind a second call.

Do it in this order
Why it matters

Every event-driven integration has this two-phase shape: the notification, then the fetch.

Go deeper

In one line: Apify Integrations -> HTTP Webhook (POST, on run succeeded) -> n8n Webhook node (POST, listening) delivers metadata with defaultDatasetId.

Top actor: 54k ratings, 4.4 stars; demo count 1-2; scales to 100k (l3186054 0:17-0:19)

Apify Integrations -> HTTP Webhook (generic) (l3186054 0:22-0:23)

Webhook node method GET -> POST because Apify posts (l3186054 0:24-0:25)

Trigger 'run succeeded' so it fires once, after completion (l3186054 0:26, 0:36)

Listen for test event first or nothing is captured (l3186054 0:37)

Payload = metadata + defaultDatasetId, no contact data yet (l3186054 0:42-0:43)

▶ Watch this taught:

03

Import cURL: the HTTP Request node for any API in the world

how-to

Every API on earth documents itself in the same cURL shape. Paste it and the node configures itself.

Open Apify's API docs for 'get dataset items', copy the cURL, click Import cURL on n8n's HTTP Request node - method, headers and URL populate. 'This is the most coolest trick... any kind of API that you're actually using will be in the same curl format.' Two edits: the Authorization header's placeholder becomes your personal Apify token (Settings -> API & Integrations -> Create new token), typed after 'Bearer' with exactly one space; the literal 'dataset' in the URL becomes an expression by dragging defaultDatasetId from the webhook output. Execute step returns the full business record - email included. Office-hour footnote: the node's authentication dropdown (header auth, bearer, generic vs predefined credential) must match what the target API's documentation says; it is not guessable.

Do it in this order
Why it matters

Together with Day 7's version, this is the whole 'unsupported app' toolkit - and it is why Paul's Catalyst sessions treat cURL as lingua franca.

Go deeper

In one line: HTTP Request node + Import cURL from the API docs; replace token and placeholders; auth type per the API docs.

Import cURL auto-fills method, headers, URL (l3186054 0:58)

Personal token: Apify Settings -> API & Integrations -> Create token; 'Bearer' + space + token (l3186054 1:01-1:08)

URL 'dataset' placeholder -> dragged defaultDatasetId expression (l3186054 1:04-1:07)

General technique for any API with a cURL example (l3186054 0:59)

Auth type must follow the API's docs (header/bearer/generic/predefined) (l3319320 0:51-0:52)

▶ Watch this taught:

04

AI Agent + Structured Output Parser -> Gmail draft (never send)

how-to

'I don't trust AI to always follow my instructions.' Neither should you - so make the parser enforce the shape.

AI Agent node with an OpenAI Chat Model sub-node (GPT-5 or GPT-5 mini; key from platform.openai.com -> Dashboard -> API keys). Prompt source 'Define below' -> System Message: act as a cold-email writer selling paper to law firms under the brand 'Dunder M', professional tone, reply strictly as JSON with subject and body, body in HTML. User message: the recipient email and firm name dragged from the HTTP node output (one email per listing). Add a Structured Output Parser, toggle 'Require Specific Output Format', paste the example {"subject": "...", "email": "..."}. Gmail node: Create a Draft - not Send - map Subject and Message from the parsed output, and switch the message type from Text to HTML or the formatting breaks. Full run on Los Angeles law firms produced two correct drafts. Watch the OAuth account picker - he chose the wrong Google account live.

Do it in this order
Why it matters

Draft-not-send is the human-in-the-loop that keeps a scraper-fed outreach engine from becoming a spam cannon.

Go deeper

In one line: Agent with System Message + Structured Output Parser (required format, JSON example) -> Gmail Create Draft in HTML; humans send.

GPT-5 / GPT-5 mini via API key; System Message via 'Define below' (l3186054 1:15-1:18)

Strict JSON subject+body, HTML body (l3186054 1:18-1:19)

Structured Output Parser with required format + example schema (l3186054 1:24-1:26)

Gmail Create a Draft for review, not Send (l3186054 1:27, 1:32)

Message type must be HTML (l3186054 1:29)

Two drafts verified live (l3186054 1:32-1:36)

▶ Watch this taught:

05

Easy mode: native Apify nodes replace the webhook and the cURL

Everything you just built by hand collapses into two dropdown nodes. He made you do it the hard way on purpose.

n8n's native Apify integration: 'Run an Actor' (pick the actor; paste its input as JSON copied from Apify's JSON tab, not the Form tab) chained to 'Get Dataset Items' with input {{ $json.defaultDatasetId }}. Same Apify API key as a credential. Right-click the old Webhook trigger -> Deactivate before testing. Identical output, no webhook, no cURL. 'Hard mode was taught only to explain the underlying webhook/API concepts' - production defaults to native nodes whenever one exists; the Webhook/HTTP pair is for the Fireflies of the world.

Why it matters

Knowing both lets you read any n8n template - and choose the maintainable one.

Go deeper

In one line: Native app nodes (Run an Actor -> Get Dataset Items) are the production path; the manual Webhook + HTTP build is the teaching path and the fallback for unsupported apps.

Run an Actor: actor dropdown + input JSON from the actor's JSON tab (l3186054 1:39-1:40)

Get Dataset Items: {{ $json.defaultDatasetId }} (l3186054 1:37-1:38)

Apify credential = same API key (l3186054 1:40-1:41)

Deactivate the old trigger before re-testing (l3186054 1:42)

Hard mode = pedagogy; native nodes = production (l3186054 1:46-1:48)

▶ Watch this taught:

06

Selling automations: JSON template vs retainer, credits vs margin, one key per client

The workflow you built is a few kilobytes of JSON. Whether you sell the file or the service is the whole business model.

Model 1: sell the JSON workflow as a template - 'it's gonna be in KB, not even MB.' Model 2: agency retainer - you build, host, connect the client's APIs and charge monthly; never hand over the JSON. For a vibe-coded front end on an n8n backend, price as pay-as-you-go credits ('$10 for a thousand credits') or a flat fee with margin (client pays $10, you pay $4 in API cost, keep $6). Issue a separate API key per client so usage is trackable and billing transparent. For small businesses without privacy sensitivity, recommend n8n Cloud over self-hosting so they never see ngrok and public endpoints. GoHighLevel is a CRM - layer n8n on top for automations rather than choosing between them. Compliance: the major voice/automation tools carry GDPR/HIPAA; for sensitive clients offer local or single-tenant hosting.

Why it matters

This is the pricing conversation Paul has with every agency prospect, stated in one place.

Go deeper

In one line: Sell the template or run the service; credit packs or flat-fee-with-margin; per-client API keys; n8n Cloud for small clients; GHL as CRM plus n8n.

JSON template sale vs agency retainer with hosting (l3319320 0:10-0:11)

Credits ($10/1,000) or flat fee with margin ($10 charged, $4 cost) (l3319320 0:33-0:34)

Separate API key per client for tracking and billing (l3319320 0:31-0:32)

Small clients: n8n Cloud, not Docker/self-host (l3319320 0:30-0:31)

Keep the backend JSON proprietary under a managed service (l3319320 0:30-0:31)

GHL = CRM; automate with n8n on top (l3319320 0:35-0:36)

Compliance built into major tools; local/tenant hosting for sensitive data (l3319320 0:11-0:12)

▶ Watch this taught:

07

Ollama sizing, Genspark, Hugging Face, RunPod: running models where you are

An 18 GB laptop will not run a 671-billion-parameter model. Ask Perplexity which one it will run.

Size models to hardware: an Apple M3 Pro with 18 GB runs up to ~14B parameters comfortably; DeepSeek's 671B flagship needs a datacentre. Reference disk sizes read live: GPT-OSS-20B ~14 GB, a DeepSeek distil ~5 GB, 7B ~4.7 GB, 14B ~9 GB; models live on local disk (check in the Ollama app). Tip: paste your specs into Perplexity and ask which version to download. Ollama now exposes Anthropic-compatible endpoints so Claude Code can run against local models. A local model is only up while your machine is - for always-on you self-host on a server and expose an API. Genspark Browser: free, runs offline with local models, ships task agents (slides, sheets, docs, developer, designer, voice) including a voice agent that finds a restaurant on Google Maps and phones to book. Hugging Face for domain fine-tunes (medical); RunPod for hourly GPU rental when local is not enough. Also: Windsurf described as 'no longer active'; Google Antigravity as the new local IDE; Claude Code's 'Ralph loop' plugin for autonomous looping.

Why it matters

Extends Day 1's local-LLM concept with the sizing numbers people actually need.

Go deeper

In one line: Parameter count vs RAM (18 GB ~ 14B); disk sizes 5-14 GB; Perplexity for hardware matching; Ollama + Claude Code; Genspark for offline agents; RunPod for rented GPUs.

M3 Pro 18 GB -> ~14B max; 671B DeepSeek is out of reach (l3319320 0:12-0:13)

Disk: GPT-OSS-20B ~14 GB, DeepSeek ~5 GB, 7B ~4.7 GB, 14B ~9 GB (l3319320 0:39-0:40)

Ollama supports Claude Code via Anthropic-compatible API (l3319320 0:14)

Local = online only while the machine is on; self-host for always-on (l3319320 0:24-0:25)

Genspark Browser: offline agents incl. a restaurant-booking voice agent (l3319320 0:15-0:16)

Hugging Face for niche fine-tunes; RunPod for hourly GPUs (l3319320 0:16-0:17)

▶ Watch this taught:

Every concept, three clicks deep

The same concepts as a quick reference: the closed row is the glance, open is the study card, and every timestamp jumps into the recording.

01Webhook vs API, GET vs POST - the hotel and the waiterWebhook = passive one-way event notification into your workflow;

Webhook = passive one-way event notification into your workflow; API = two-way request/response you initiate; GET reads, POST writes; prefer native nodes, fall back to Webhook/HTTP.

Webhook 'always listening', single-direction, event-fired (l3186054 0:24)

API bidirectional: send, process on server, response returns (l3186054 0:43)

GET = retrieve; POST = submit/create (l3186054 1:12)

Seen before: Webhook in the voice session, HTTP in the Jerry/Fireflies session (l3186054 0:01-0:03)

SOP: native node when available; Webhook/HTTP when not (l3186054 1:50)

02Apify actor -> n8n webhook: the payload is a dataset ID, not the dataApify Integrations -> HTTP Webhook (POST, on run succeeded) -> n8n Webhook node (POST, listening) delivers…

Apify Integrations -> HTTP Webhook (POST, on run succeeded) -> n8n Webhook node (POST, listening) delivers metadata with defaultDatasetId.

Top actor: 54k ratings, 4.4 stars; demo count 1-2; scales to 100k (l3186054 0:17-0:19)

Apify Integrations -> HTTP Webhook (generic) (l3186054 0:22-0:23)

Webhook node method GET -> POST because Apify posts (l3186054 0:24-0:25)

Trigger 'run succeeded' so it fires once, after completion (l3186054 0:26, 0:36)

Listen for test event first or nothing is captured (l3186054 0:37)

Payload = metadata + defaultDatasetId, no contact data yet (l3186054 0:42-0:43)

03Import cURL: the HTTP Request node for any API in the worldHTTP Request node + Import cURL from the API docs;

HTTP Request node + Import cURL from the API docs; replace token and placeholders; auth type per the API docs.

Import cURL auto-fills method, headers, URL (l3186054 0:58)

Personal token: Apify Settings -> API & Integrations -> Create token; 'Bearer' + space + token (l3186054 1:01-1:08)

URL 'dataset' placeholder -> dragged defaultDatasetId expression (l3186054 1:04-1:07)

General technique for any API with a cURL example (l3186054 0:59)

Auth type must follow the API's docs (header/bearer/generic/predefined) (l3319320 0:51-0:52)

04AI Agent + Structured Output Parser -> Gmail draft (never send)Agent with System Message + Structured Output Parser (required format, JSON example) -> Gmail Create Draft…

Agent with System Message + Structured Output Parser (required format, JSON example) -> Gmail Create Draft in HTML; humans send.

GPT-5 / GPT-5 mini via API key; System Message via 'Define below' (l3186054 1:15-1:18)

Strict JSON subject+body, HTML body (l3186054 1:18-1:19)

Structured Output Parser with required format + example schema (l3186054 1:24-1:26)

Gmail Create a Draft for review, not Send (l3186054 1:27, 1:32)

Message type must be HTML (l3186054 1:29)

Two drafts verified live (l3186054 1:32-1:36)

05Easy mode: native Apify nodes replace the webhook and the cURLNative app nodes (Run an Actor -> Get Dataset Items) are the production path;

Native app nodes (Run an Actor -> Get Dataset Items) are the production path; the manual Webhook + HTTP build is the teaching path and the fallback for unsupported apps.

Run an Actor: actor dropdown + input JSON from the actor's JSON tab (l3186054 1:39-1:40)

Get Dataset Items: {{ $json.defaultDatasetId }} (l3186054 1:37-1:38)

Apify credential = same API key (l3186054 1:40-1:41)

Deactivate the old trigger before re-testing (l3186054 1:42)

Hard mode = pedagogy; native nodes = production (l3186054 1:46-1:48)

06Selling automations: JSON template vs retainer, credits vs margin, one key per clientSell the template or run the service;

Sell the template or run the service; credit packs or flat-fee-with-margin; per-client API keys; n8n Cloud for small clients; GHL as CRM plus n8n.

JSON template sale vs agency retainer with hosting (l3319320 0:10-0:11)

Credits ($10/1,000) or flat fee with margin ($10 charged, $4 cost) (l3319320 0:33-0:34)

Separate API key per client for tracking and billing (l3319320 0:31-0:32)

Small clients: n8n Cloud, not Docker/self-host (l3319320 0:30-0:31)

Keep the backend JSON proprietary under a managed service (l3319320 0:30-0:31)

GHL = CRM; automate with n8n on top (l3319320 0:35-0:36)

Compliance built into major tools; local/tenant hosting for sensitive data (l3319320 0:11-0:12)

07Ollama sizing, Genspark, Hugging Face, RunPod: running models where you areParameter count vs RAM (18 GB ~ 14B);

Parameter count vs RAM (18 GB ~ 14B); disk sizes 5-14 GB; Perplexity for hardware matching; Ollama + Claude Code; Genspark for offline agents; RunPod for rented GPUs.

M3 Pro 18 GB -> ~14B max; 671B DeepSeek is out of reach (l3319320 0:12-0:13)

Disk: GPT-OSS-20B ~14 GB, DeepSeek ~5 GB, 7B ~4.7 GB, 14B ~9 GB (l3319320 0:39-0:40)

Ollama supports Claude Code via Anthropic-compatible API (l3319320 0:14)

Local = online only while the machine is on; self-host for always-on (l3319320 0:24-0:25)

Genspark Browser: offline agents incl. a restaurant-booking voice agent (l3319320 0:15-0:16)

Hugging Face for niche fine-tunes; RunPod for hourly GPUs (l3319320 0:16-0:17)

Tools referenced

ToolCoverageMomentContext
ApifydemonstratedGoogle Maps email actor; Integrations webhook; API token; native n8n nodes
n8ndemonstratedWebhook, HTTP Request (Import cURL), AI Agent, Structured Output Parser, Gmail, native Apify nodes
OpenAI APIdemonstratedGPT-5 / GPT-5 mini chat model
GmaildemonstratedCreate a Draft in HTML
OllamaexplainedSizing, disk usage, Claude Code support
GensparkexplainedOffline browser agents incl. voice booking
GoHighLevelexplainedCRM; n8n on top; duplicate-contact error case
Hugging FacementionedDomain fine-tuned models
RunPodmentionedHourly GPU rental
UnipilementionedUnofficial WhatsApp API
VercelmentionedFront-end hosting with GitHub import and custom domain
HiggsfieldmentionedKling 2.6 + Nano Banana for location-video edits
Claude CodementionedRalph loop plugin; same prompting transfers to Bolt/Lovable
Google AntigravitymentionedDescribed as the new local IDE

Action items

    Resources mentioned

    Resources
    • docDay 9 workbook - rebuild hard mode exactly
    • docOffice Hour 5 question log (~40 items)

    Extraction notes

    This page was built from an auto-generated transcript, which garbles product and people's names. Those were corrected silently in everything above and logged here for transparency. The warnings flag claims that were true on the recording day but change fast.

    Transcript corrections applied

    The transcript saysThe trainer actually means
    Edoku GroupAkshat's employer - unverified spelling
    run portRunPod
    voice mail (hosting platform)Vercel
    Hunyan videoTencent Hunyuan Video
    anti gravityGoogle Antigravity
    Vocal Labs / VocalAppsunresolved voice-agent tool with WhatsApp integration
    DHL ... HIPAA compliancegarbled compliance remark
    UnipylUnipile

    True on recording day — verify before relying