This workflow corresponds to n8n.io template #10781 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "b3ebac65-12a0-4329-b543-bbb5c0d5c23b",
"name": "Other Error",
"type": "n8n-nodes-base.set",
"position": [
-800,
80
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "status-other-error",
"name": "status",
"type": "string",
"value": "\u274c \u305d\u306e\u4ed6\u306e\u30a8\u30e9\u30fc"
},
{
"id": "error-type-other",
"name": "error_type",
"type": "string",
"value": "={{ $json.errorType }}"
},
{
"id": "error-message-other",
"name": "error_message",
"type": "string",
"value": "={{ $json.errorMessage || $json.message }}"
},
{
"id": "timestamp-error",
"name": "checked_at",
"type": "string",
"value": "={{ $now.toISO() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a3dfa5f8-abff-4a5b-a06e-e600e94e221d",
"name": "No Tweet Found",
"type": "n8n-nodes-base.set",
"position": [
-800,
224
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "status-no-tweet",
"name": "status",
"type": "string",
"value": "\ud83d\udced \u30c4\u30a4\u30fc\u30c8\u306a\u3057"
},
{
"id": "reason-no-tweet",
"name": "reason",
"type": "string",
"value": "\u76e3\u8996\u5bfe\u8c61\u30e6\u30fc\u30b6\u30fc\u304b\u3089\u306e\u30c4\u30a4\u30fc\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f"
},
{
"id": "timestamp-no-tweet",
"name": "checked_at",
"type": "string",
"value": "={{ $now.toISO() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "91e51d65-cbac-4fc0-95b2-ec77f6db281a",
"name": "Is No Tweets?",
"type": "n8n-nodes-base.if",
"position": [
-1024,
80
],
"parameters": {
"options": {},
"conditions": {
"string": [
{
"value1": "={{ $json.status }}",
"value2": "no_tweets",
"operation": "equals"
}
]
}
},
"typeVersion": 2
},
{
"id": "1fde1a49-89b3-463e-94cf-9cbd54a506b9",
"name": "Rate Limit Error",
"type": "n8n-nodes-base.set",
"position": [
-1024,
224
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "status-rate-limit",
"name": "status",
"type": "string",
"value": "\u26a0\ufe0f API\u5236\u9650\u30a8\u30e9\u30fc"
},
{
"id": "reason-rate-limit",
"name": "reason",
"type": "string",
"value": "Twitter API\u306e\u30ec\u30fc\u30c8\u5236\u9650\u306b\u9054\u3057\u307e\u3057\u305f\u300215\u5206\u5f8c\u306b\u81ea\u52d5\u7684\u306b\u56de\u5fa9\u3057\u307e\u3059\u3002"
},
{
"id": "error-detail",
"name": "error_message",
"type": "string",
"value": "={{ $json.errorMessage }}"
},
{
"id": "timestamp-rate",
"name": "checked_at",
"type": "string",
"value": "={{ $now.toISO() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "6e668adb-3f14-4c2f-80b2-4f8299998171",
"name": "Is Rate Limit?",
"type": "n8n-nodes-base.if",
"position": [
-1248,
80
],
"parameters": {
"options": {},
"conditions": {
"string": [
{
"value1": "={{ $json.errorType }}",
"value2": "rate_limit",
"operation": "equals"
}
]
}
},
"typeVersion": 2
},
{
"id": "80fd702a-9366-4a8c-8682-160632bd63ec",
"name": "Reply Error",
"type": "n8n-nodes-base.set",
"position": [
-576,
-128
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "error-status",
"name": "status",
"type": "string",
"value": "\u274c \u30ea\u30d7\u30e9\u30a4\u9001\u4fe1\u30a8\u30e9\u30fc"
},
{
"id": "error-message",
"name": "error_message",
"type": "string",
"value": "={{ $json.error }}"
},
{
"id": "tweet-id",
"name": "tweet_id",
"type": "string",
"value": "={{ $('Prepare Reply').item.json.replyToTweetId }}"
},
{
"id": "checked-time",
"name": "checked_at",
"type": "string",
"value": "={{ $now.toISO() }}"
},
{
"id": "api-response",
"name": "api_response",
"type": "object",
"value": "={{ $json }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a24a0504-049d-4054-9082-a7195e4e44ee",
"name": "Already Replied (Skip)",
"type": "n8n-nodes-base.set",
"position": [
-576,
32
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "duplicate-status",
"name": "status",
"type": "string",
"value": "\u2705 \u65e2\u306b\u30ea\u30d7\u30e9\u30a4\u6e08\u307f\uff08\u30b9\u30ad\u30c3\u30d7\uff09"
},
{
"id": "duplicate-reason",
"name": "reason",
"type": "string",
"value": "\u3053\u306e\u30c4\u30a4\u30fc\u30c8\u306b\u306f\u65e2\u306b\u30ea\u30d7\u30e9\u30a4\u3092\u9001\u4fe1\u6e08\u307f\u3067\u3059\u3002\u91cd\u8907\u30ea\u30d7\u30e9\u30a4\u306f\u9001\u4fe1\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002"
},
{
"id": "tweet-id",
"name": "tweet_id",
"type": "string",
"value": "={{ $('Prepare Reply').item.json.replyToTweetId }}"
},
{
"id": "username",
"name": "target_username",
"type": "string",
"value": "={{ $('Prepare Reply').item.json.extractedUsername }}"
},
{
"id": "checked-time",
"name": "checked_at",
"type": "string",
"value": "={{ $now.toISO() }}"
},
{
"id": "original-tweet",
"name": "original_tweet",
"type": "string",
"value": "={{ $('Prepare Reply').item.json.originalTweet }}"
},
{
"id": "error-detail",
"name": "api_message",
"type": "string",
"value": "={{ $json.error }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "534ae83f-b785-4605-b5e0-20e997525f54",
"name": "Reply Success",
"type": "n8n-nodes-base.set",
"position": [
-576,
-288
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "success-status",
"name": "status",
"type": "string",
"value": "\u2705 \u30ea\u30d7\u30e9\u30a4\u9001\u4fe1\u6210\u529f"
},
{
"id": "reply-to",
"name": "replied_to_tweet_id",
"type": "string",
"value": "={{ $('Prepare Reply').item.json.replyToTweetId }}"
},
{
"id": "reply-time",
"name": "replied_at",
"type": "string",
"value": "={{ $now.toISO() }}"
},
{
"id": "reply-text",
"name": "reply_text",
"type": "string",
"value": "={{ $('Prepare Reply').item.json.replyText }}"
},
{
"id": "original-tweet",
"name": "original_tweet",
"type": "string",
"value": "={{ $('Prepare Reply').item.json.originalTweet }}"
},
{
"id": "username",
"name": "target_username",
"type": "string",
"value": "={{ $('Prepare Reply').item.json.extractedUsername }}"
},
{
"id": "api-response",
"name": "api_response",
"type": "object",
"value": "={{ $json }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "36704cb4-d6f0-49b0-b461-8be94d685324",
"name": "Is Duplicate?",
"type": "n8n-nodes-base.if",
"position": [
-800,
-144
],
"parameters": {
"options": {},
"conditions": {
"string": [
{
"value1": "={{ $json.error }}",
"value2": "duplicate content",
"operation": "contains"
}
]
}
},
"typeVersion": 2
},
{
"id": "8a16457f-d3b7-4725-bc95-971c9bfc3ec7",
"name": "Send Reply (HTTP)",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1024,
-144
],
"parameters": {
"url": "https://api.twitter.com/2/tweets",
"method": "POST",
"options": {},
"jsonBody": "={{ { \"text\": $json.replyText, \"reply\": { \"in_reply_to_tweet_id\": $json.replyToTweetId } } }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "twitterOAuth2Api"
},
"credentials": {},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "3085cff0-b825-4bcc-b011-411dcc069199",
"name": "Prepare Reply",
"type": "n8n-nodes-base.code",
"position": [
-1248,
-144
],
"parameters": {
"jsCode": "// === \u4fee\u6b63\u7248 Prepare Reply ===\n\n// \u5404item\u3054\u3068\u306b\u30ea\u30d7\u30e9\u30a4\u30c7\u30fc\u30bf\u3092\u751f\u6210\nreturn items.map(item => {\n const tweet = item.json.text;\n const tweetId = item.json.id;\n const authorId = item.json.author_id;\n const includes = item.json.includes || {};\n const users = includes.users || [];\n\n // \u30e6\u30fc\u30b6\u30fc\u540d\u3092\u53d6\u5f97\n let username = 'unknown';\n const user = users.find(u => u.id === authorId);\n if (user && user.username) {\n username = user.username;\n }\n\n // \u56fa\u5b9a\u30e1\u30c3\u30bb\u30fc\u30b8\uff08\u5fc5\u8981\u306a\u3089\u3053\u3053\u3092\u500b\u5225\u5bfe\u5fdc\u306b\u5909\u66f4\u53ef\uff09\n const replyMessage = `\u5f0a\u793e\u306e\u65b0\u30b5\u30fc\u30d3\u30b9\u3092\u3054\u89a7\u304f\u3060\u3055\u3044\uff01 https://x.com/linepi_pi/status/1988478476761133063`;\n\n return {\n json: {\n ...item.json,\n replyText: replyMessage,\n replyToTweetId: tweetId,\n extractedUsername: username,\n originalTweet: tweet,\n },\n };\n});\n"
},
"typeVersion": 2
},
{
"id": "e88fb60b-4a53-4880-9060-b32cae3ee7f1",
"name": "Is Success?",
"type": "n8n-nodes-base.if",
"position": [
-1472,
-32
],
"parameters": {
"options": {},
"conditions": {
"string": [
{
"value1": "={{ $json.status }}",
"value2": "success",
"operation": "equals"
}
]
}
},
"typeVersion": 2
},
{
"id": "767a017c-52a9-4927-80b0-9075c233a5f9",
"name": "Get Latest Tweet Per Account",
"type": "n8n-nodes-base.code",
"position": [
-1696,
-32
],
"parameters": {
"jsCode": "// === \u4fee\u6b63\u7248 ===\n// Twitter API\u306e\u30ec\u30b9\u30dd\u30f3\u30b9\u3092\u3059\u3079\u3066\u306eitems\u304b\u3089\u7d50\u5408\nconst allResponses = items.map(item => item.json);\n\n// \u30c7\u30fc\u30bf\u3092\u7d71\u5408\nconst mergedData = {\n data: [],\n includes: { users: [] },\n};\n\nfor (const res of allResponses) {\n if (res.data) mergedData.data.push(...res.data);\n if (res.includes?.users) {\n const existingUserIds = new Set(mergedData.includes.users.map(u => u.id));\n for (const user of res.includes.users) {\n if (!existingUserIds.has(user.id)) {\n mergedData.includes.users.push(user);\n }\n }\n }\n}\n\n// \u30a8\u30e9\u30fc\u30c1\u30a7\u30c3\u30af\nif (mergedData.error || mergedData.errors) {\n const errorMessage =\n mergedData.error?.message ||\n mergedData.errors?.[0]?.message ||\n JSON.stringify(mergedData.error || mergedData.errors);\n\n let errorType = 'unknown_error';\n if (errorMessage.includes('Too Many Requests') || errorMessage.includes('Rate limit')) {\n errorType = 'rate_limit';\n } else if (errorMessage.includes('Unauthorized') || errorMessage.includes('authentication')) {\n errorType = 'auth_error';\n } else if (errorMessage.includes('Bad request')) {\n errorType = 'bad_request';\n }\n\n return [\n {\n json: {\n status: 'error',\n errorType,\n errorMessage,\n },\n },\n ];\n}\n\nconst tweets = mergedData.data || [];\nconst users = mergedData.includes.users || [];\n\nif (tweets.length === 0) {\n return [\n {\n json: {\n status: 'no_tweets',\n message: '\u76e3\u8996\u5bfe\u8c61\u30e6\u30fc\u30b6\u30fc\u304b\u3089\u306e\u30c4\u30a4\u30fc\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f',\n },\n },\n ];\n}\n\n// author_id\u3054\u3068\u306b\u6700\u65b0\u30c4\u30a4\u30fc\u30c8\u306e\u307f\u53d6\u5f97\nconst latestTweetsByAuthor = {};\nfor (const tweet of tweets) {\n const authorId = tweet.author_id;\n if (\n !latestTweetsByAuthor[authorId] ||\n new Date(tweet.created_at) > new Date(latestTweetsByAuthor[authorId].created_at)\n ) {\n latestTweetsByAuthor[authorId] = tweet;\n }\n}\n\n// \u7d50\u679c\u914d\u5217\nconst latestTweets = Object.values(latestTweetsByAuthor);\n\n// includes\u3092\u518d\u4ed8\u4e0e\u3057\u3066\u8fd4\u3059\nreturn latestTweets.map(tweet => ({\n json: {\n ...tweet,\n includes: { users },\n status: 'success',\n totalAccountsFound: latestTweets.length,\n totalTweetsScanned: tweets.length,\n },\n}));\n"
},
"typeVersion": 2
},
{
"id": "7568127e-1144-41b1-b863-f56c9b1b044d",
"name": "Search Tweets (HTTP)",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1904,
-32
],
"parameters": {
"url": "https://api.twitter.com/2/tweets/search/recent",
"options": {},
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "query",
"value": "from:badassceo OR from:bozu_108 OR from:hirox246 OR from:takaichi_sanae -is:retweet"
},
{
"name": "tweet.fields",
"value": "created_at,author_id"
},
{
"name": "expansions",
"value": "author_id"
},
{
"name": "user.fields",
"value": "username"
},
{
"name": "max_results",
"value": "50"
}
]
},
"nodeCredentialType": "twitterOAuth2Api"
},
"credentials": {},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "e20df0a0-fe20-4830-856d-4d4504c75947",
"name": "Schedule Trigger (15\u5206\u3054\u3068)",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-2112,
-32
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 30
}
]
}
},
"typeVersion": 1.2
},
{
"id": "cd49a9e5-36a7-4327-91ef-156ab7221e1e",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2528,
-256
],
"parameters": {
"width": 304,
"height": 608,
"content": "This workflow automates your X (Twitter) engagement. It runs on a schedule, searches for new tweets based on a query, and automatically sends a reply.\n\n### How it works\n1. **Trigger:** Runs the workflow automatically at your chosen interval (e.read, every 15 minutes).\n2. **Search:** Uses the X (Twitter) API v2 to find recent tweets matching your query.\n3. **Reply:** Posts a pre-defined reply to the tweet.\n4. **Handle Errors:** Includes logic to manage API rate limits and avoid sending duplicate replies.\n\n### Setup steps\n1. **Add Credentials:** You must have an X (Twitter) Developer Account (v2). Add your credentials to n8n.\n2. **Set Query:** In the \"Search Tweets\" node (Section 1), you MUST define your search query (e.g., `#n8n`).\n3. **Customize Reply:** In the \"Prepare Reply\" node (Section 2), you MUST write the reply text you want to send."
},
"typeVersion": 1
},
{
"id": "985fc4bc-80f5-4395-89a6-2a72a86373ec",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2160,
-128
],
"parameters": {
"color": 7,
"width": 832,
"height": 256,
"content": "## 1. Search for Tweets\nThis section runs on a schedule and fetches new tweets based on your query."
},
"typeVersion": 1
},
{
"id": "9d5b2a74-46ba-4f41-ac28-16875202cd9f",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1280,
-256
],
"parameters": {
"color": 7,
"width": 656,
"height": 272,
"content": "## 2. Process & Send Reply\nThis section prepares and sends your defined reply to the new tweets."
},
"typeVersion": 1
},
{
"id": "0053ada0-8be0-4a8c-8340-0dd0a0bf76d5",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1280,
64
],
"parameters": {
"color": 7,
"width": 656,
"height": 352,
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## 3. Error Handling\nThis section catches common errors like API rate limits or empty search results."
},
"typeVersion": 1
}
],
"connections": {
"Is Success?": {
"main": [
[
{
"node": "Prepare Reply",
"type": "main",
"index": 0
}
],
[
{
"node": "Is Rate Limit?",
"type": "main",
"index": 0
}
]
]
},
"Is Duplicate?": {
"main": [
[
{
"node": "Already Replied (Skip)",
"type": "main",
"index": 0
}
],
[
{
"node": "Reply Success",
"type": "main",
"index": 0
}
]
]
},
"Is No Tweets?": {
"main": [
[
{
"node": "No Tweet Found",
"type": "main",
"index": 0
}
],
[
{
"node": "Other Error",
"type": "main",
"index": 0
}
]
]
},
"Prepare Reply": {
"main": [
[
{
"node": "Send Reply (HTTP)",
"type": "main",
"index": 0
}
]
]
},
"Is Rate Limit?": {
"main": [
[
{
"node": "Rate Limit Error",
"type": "main",
"index": 0
}
],
[
{
"node": "Is No Tweets?",
"type": "main",
"index": 0
}
]
]
},
"Send Reply (HTTP)": {
"main": [
[
{
"node": "Is Duplicate?",
"type": "main",
"index": 0
}
]
]
},
"Search Tweets (HTTP)": {
"main": [
[
{
"node": "Get Latest Tweet Per Account",
"type": "main",
"index": 0
}
]
]
},
"Get Latest Tweet Per Account": {
"main": [
[
{
"node": "Is Success?",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger (15\u5206\u3054\u3068)": {
"main": [
[
{
"node": "Search Tweets (HTTP)",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automates your X (Twitter) engagement by acting as an auto-responder. It runs on a schedule, searches for new tweets based on a specific query (like a hashtag, keyword, or mention), and automatically sends a reply. Schedule Trigger: Runs the workflow automatically…
Source: https://n8n.io/workflows/10781/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Marketing teams and social media managers in Japan who want to automate content creation while maintaining high quality standards and cultural appropriateness. Perfect for businesses that need consist
This n8n workflow is designed for content curators, digital marketers, and social media managers who want to automate the process of discovering, translating, and publishing news content from multiple
This template is ideal for sales teams, recruiters, business development professionals, and relationship managers who need to monitor changes in their network's LinkedIn profiles. Perfect for agencies
Automatically discovers trending topics in your niche and generates ready-to-use content ideas with AI. Twitter/X trending topics and hashtags Reddit hot posts from niche subreddits Google Trends dail
Social media managers, creators, and brand accounts that rely on retweets for reach but want an automated, hands-off cleanup after campaigns to keep profiles tidy and on-brand.