n8n
Use JobsPipe in n8n - importable workflow templates for daily job feeds to Google Sheets, Signal webhooks to Slack, company hiring signals and an AI agent on the JobsPipe MCP server, plus credential setup and the JSON body settings that avoid a 400.
n8n calls JobsPipe with its HTTP Request node, receives Signal deliveries with its Webhook node, and connects an AI agent to the JobsPipe MCP server with its MCP Client Tool node. The four templates below are ready to import: set up one credential, edit the filters, and run.
What you can build
| Template | Trigger | What it does | Credits |
|---|---|---|---|
| Daily new jobs to Google Sheets | Every day | Fetches the postings JobsPipe found since the last run and adds them to a sheet | 1 per new job |
| Signal to Slack | Signal webhook | Verifies each Signal delivery and posts every match to a Slack channel | None |
| Company hiring signals | Every Monday | Counts each target company's postings from the last 7 days and logs them to a sheet | At most 1 per company per run |
| AI agent with the JobsPipe MCP server | Chat message | An AI Agent that searches jobs, reads postings and creates Signals | 1 per job an agent search returns |
The templates were built and tested on n8n 2.40. They use current node versions, so an older n8n may not recognise every node; update n8n if a node shows as unknown after import.
Import a template
In n8n, create a workflow, open the ... menu next to the workflow name at the top left, and choose Import → From URL.
Paste the template's URL, for example https://docs.jobspipe.dev/templates/n8n/jobspipe-daily-jobs-to-google-sheets.json. You can also download the file and choose Import → From file, or open it and paste its contents onto the canvas.
Read the yellow sticky note on the canvas: it lists what to fill in for that template.
The files contain no keys or secrets. Credentials are referenced by name only: create the JobsPipe credential before you import, and the editor connects it for you.
Set up the JobsPipe credential
All the templates share one Header Auth credential. Create it once:
- Create an API key in the dashboard under Settings → API Keys. It starts with
jp_live_. See Authentication. - In n8n, open Credentials → Create credential, search for Header Auth, and fill it in:
| Field | Value |
|---|---|
| Credential name | JobsPipe API |
| Name | Authorization |
| Value | Bearer jp_live_your_key_here |
The value is the word Bearer, one space, then your key. The templates look for a credential named JobsPipe API: if it exists when you import a template, the editor connects it to every JobsPipe node. If you create it after importing, open each JobsPipe node once and pick it under Header Auth.
To call JobsPipe from your own HTTP Request node, set Authentication to Generic Credential Type, Generic Auth Type to Header Auth, and pick JobsPipe API. Don't paste the key into a header field on the node itself; a credential keeps it out of exported workflows.
Send the JSON body correctly
POST /v1/jobs/search takes a JSON object. Most failed n8n requests come from a body that stopped being valid JSON after an expression was filled in. In the HTTP Request node:
- Turn on Send Body.
- Set Body Content Type to JSON.
- Set Specify Body to Using JSON and write the whole body as literal JSON:
{
"job_title_or": ["data engineer", "analytics engineer"],
"job_country_code_or": ["US"],
"posted_at_max_age_days": 7,
"limit": 100
}To put values from earlier nodes into the body, switch the field to Expression and keep the JSON quoting yourself. An expression inserts text, not JSON: a string lands without quotes, and an array lands as data engineer,analyst with no brackets or quotes. Wrap every value that is not a plain number in JSON.stringify(...), which adds the quotes and brackets:
{
"job_title_or": {{ JSON.stringify($json.titles) }},
"job_country_code_or": [{{ JSON.stringify($json.country) }}],
"posted_at_max_age_days": {{ Number($json.days) }},
"limit": 100
}With titles = ["data engineer", "analyst"], country = "US" and days = 7, n8n sends:
{"job_title_or":["data engineer","analyst"],"job_country_code_or":["US"],"posted_at_max_age_days":7,"limit":100}These are the mistakes to avoid:
| In the body field | What is sent | Result |
|---|---|---|
"job_title_or": [{{ $json.title }}] | [data engineer] | Not valid JSON |
"job_title_or": {{ $json.titles }} | data engineer,analyst | Not valid JSON |
"job_title_or": ["{{ $json.title }}"] | ["data engineer"] | Valid, but breaks when the value contains a " |
"job_title_or": {{ JSON.stringify($json.titles) }} | ["data engineer","analyst"] | Valid |
Recent n8n versions catch an invalid Using JSON body before sending it, with The value in the "JSON Body" field is not valid JSON. With Body Content Type set to Raw, or in older versions, the request is sent anyway and JobsPipe answers 400. A 400 that reaches JobsPipe costs 1 credit (see requests that fail), so fix the body rather than retrying it. Leave Retry On Fail off for JobsPipe requests unless you only retry on 429.
Filter names are strict: an unknown name is rejected with a 400 that suggests the right one. All filters are listed under filters.
Daily new jobs to Google Sheets
Download jobspipe-daily-jobs-to-google-sheets.jsonSchedule Trigger (every day at 07:00) → HTTP Request (search) → Split Out (data) → Google Sheets (append or update).
The search asks only for postings JobsPipe first discovered in the last 25 hours, with discovered_at_gte:
{
"job_title_or": ["data engineer", "analytics engineer"],
"job_country_code_or": ["US"],
"discovered_at_gte": "{{ $now.toUTC().minus({ hours: 25 }).toFormat('yyyy-MM-dd HH:mm:ss') }}",
"limit": 100
}discovered_at_gte catches postings that were published earlier but found late, which a posted-date window would miss. The expression sits inside quotes, so the body stays valid JSON. The extra hour overlaps the previous run, so a delayed run misses nothing. Jobs in the overlap cost no credits again in the same calendar month, and the sheet step matches rows on id, so they are not added twice.
The HTTP Request node pages through the results for you: its Pagination option sends metadata.next_cursor back as cursor and stops when it is null (see pagination). It waits 600 ms between pages to stay under the Free plan's 2 requests per second, and stops after 10 pages. limit is capped at your plan's page size: 25 on Free, 100 on credits plans.
To set it up: edit the JSON body (titles, countries, or any filter), and in Add or update rows pick your spreadsheet and sign in to Google. Row 1 of the sheet needs these headers: id, job_title, company, location, url, date_posted, salary_string, min_annual_salary, max_annual_salary, salary_currency, discovered_at. Any other job field can be added as a column.
Credits: 1 per job returned, once per calendar month. One run reads up to 10 pages: up to 250 jobs on Free and 1,000 on credits plans, so a broad filter can use all 1,000 Free credits in a few runs. Size a new filter first with include_total_results: true and limit: 1: you pay for one job and metadata.total_results gives the count. To cap a run, lower Max Pages under the node's Pagination option.
Signal to Slack
Download jobspipe-signal-to-slack.jsonWebhook (POST) → Code (read the signature headers and raw body) → Crypto (HMAC-SHA256) → If (signature valid, fresh, alert.matched) → Split Out (payload.data) → Slack.
A Signal is a saved search that JobsPipe checks for you. When new postings match, it POSTs a signed alert.matched event to your n8n webhook, so the workflow runs only when there is something new. Signals cost no credits. Webhook destinations are available on paid plans.
To set it up:
Import the template, open JobsPipe Signal webhook, and copy its Production URL. It must be reachable from the internet over https (n8n Cloud URLs are; a self-hosted n8n needs a public address). Then activate the workflow: the production URL only answers while the workflow is active.
In the JobsPipe dashboard open Workflows, pick a template or describe what to watch in chat, choose Webhook as the destination and paste the n8n URL. When it is created, the dialog shows whether a test delivery reached n8n.
Copy the whsec_ signing secret the dialog shows. It is shown only once. Paste it into the Secret field of the Compute HMAC node, replacing whsec_REPLACE_WITH_YOUR_SIGNING_SECRET.
Pick a channel in Post to Slack and connect your Slack account.
An agent connected to the MCP server can create the same Signal with create_signal, using a destination of { "kind": "webhook", "target": "<your n8n URL>", "cadence": "instant" }. The response carries the signing_secret once.
How verification works. The Webhook node keeps the raw request body (its Raw Body option is on). The Code node builds <X-JobsPipe-Timestamp>.<raw body> and checks that the timestamp is less than five minutes old. The Crypto node computes the hex HMAC-SHA256 of that string with your secret, and the If node compares it with the X-JobsPipe-Signature header. Deliveries that fail any check, and the alert.test hello sent when the Signal is created, go to Ignore delivery. The scheme is described under verifying signatures.
The webhook answers 200 as soon as the request arrives, so JobsPipe does not wait for Slack. Each delivery carries up to 25 matches, and the Slack node posts one message per match. To post one message per delivery instead, add an Aggregate node before Slack. Items in technology_adoption mode have a different shape (company.name, technology.name); adjust the message text if you use that mode.
Company hiring signals
Download jobspipe-company-hiring-signals.jsonSchedule Trigger (Mondays at 08:00) → Code (your company list) → HTTP Request (one search per company) → Code (summarize) → Google Sheets (append).
For each company the search sends:
{
"company_name_or": {{ JSON.stringify([$json.company]) }},
"posted_at_max_age_days": 7,
"include_total_results": true,
"limit": 1
}metadata.total_results is the number of postings the company published in the last 7 days, and the one job returned gives the latest title and link. The sheet gets one row per company per week: week_of, company, new_roles_7d, latest_title, latest_url. A week-over-week jump is the signal.
To set it up: list your accounts in Companies to watch, and pick your spreadsheet in Log to Google Sheets with those five headers in row 1. company_name_or matches the company name exactly; to match names that contain your text (for example Acme for Acme Inc), change the filter to company_name_partial_match_or. Requests are sent one per company, 600 ms apart.
Credits: at most 1 per company per run, for the one job returned, and nothing for a company with no postings that week. A job you already paid for this month is free.
AI agent with the JobsPipe MCP server
Download jobspipe-ai-agent-mcp.jsonChat Trigger → AI Agent, with an OpenAI Chat Model, Simple Memory and an MCP Client Tool pointed at https://mcp.jobspipe.dev/mcp.
The MCP Client Tool gives the agent every JobsPipe MCP tool: search, fetch, search_jobs, create_signal, get_account_info and the rest. The node settings are:
| Setting | Value |
|---|---|
| Endpoint | https://mcp.jobspipe.dev/mcp |
| Server Transport | HTTP Streamable |
| Authentication | Header Auth, with the JobsPipe API credential |
| Tools to Include | All |
To set it up: connect the JobsPipe API credential to JobsPipe MCP and an OpenAI key to OpenAI Chat Model, then click Open chat and ask something like "Who is hiring senior data engineers in Berlin this week?". To use Claude, delete the OpenAI node and attach an Anthropic Chat Model to the agent's Chat Model input. The agent's system message asks it to keep searches small, cite posting links, and offer a Signal instead of repeating a search.
Credits: each job a search returns costs 1 credit, once per calendar month. create_signal, list_signals, get_account_info and search_documentation cost none.
To add JobsPipe to an agent you already have, attach just an MCP Client Tool node with the settings above. If you use the MCP Client Tool in a workflow instead of the HTTP Request node, the same credential works for both.
Troubleshooting
| What you see | Cause | Fix |
|---|---|---|
401 Unauthorized or Invalid API key | The credential is missing or malformed, or the key was revoked. | Check the credential: Name Authorization, Value Bearer jp_live_... with one space and no quotes. Check the node's Authentication is Generic Credential Type → Header Auth. |
402 Monthly request quota exceeded | Your credits are used up. On Free, the 1,000 starting credits do not refill. | Upgrade or wait for the monthly reset; Free credits do not reset, so pick a package. Retrying does not help, and 402 responses cost nothing. |
429 Rate limit exceeded | More requests per second than your plan allows (2 on Free). | Keep the HTTP Request node's pagination interval, or Batching under Options, at 600 ms or more on Free. 429 responses cost nothing. See rate limits. |
400 Invalid JSON body | The body was not valid JSON, usually an expression that dropped its quotes. | Use Using JSON and JSON.stringify(...), as in Send the JSON body correctly. |
400 Invalid search filters: ... | An unknown filter name or a wrong type, or a body that is a string instead of an object. | Read the message: it names the field and suggests the right one. See errors. |
| n8n: The value in the "JSON Body" field is not valid JSON | n8n caught an invalid body before sending it. | As for 400 Invalid JSON body. |
| Signal deliveries all go to Ignore delivery | The secret in Compute HMAC does not match the Signal, or the n8n server clock is off by more than five minutes. | Paste the whsec_ secret from the Signal again. If it was lost, delete the Signal and create it again to get a new one. |
| n8n: Node "Search new jobs" uses invalid credential | The workflow was imported with the n8n CLI (n8n import:workflow), which keeps the name-only credential reference instead of connecting it. | Open each JobsPipe node in the editor, pick JobsPipe API under Header Auth, and save. |
| Signal destination shows "Paused" in the dashboard | The n8n webhook failed every attempt for a day: workflow inactive, test URL used, or a redirect. | Activate the workflow, use the Production URL, then click Resume on the Signals page. |
Every response reports what it cost in metadata.credits_charged. Check your balance on the dashboard Usage page.