{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "2cbe5fe8-3593-44a8-84e6-d202e035c26d",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -144,
        -1296
      ],
      "parameters": {
        "text": "={{ $('Verify User').item.json.messageText }}",
        "options": {
          "maxIterations": 15,
          "systemMessage": "=Current time/day: {{ $now }}\n\nYou are {{ $('Verify User').item.json.userName }}'s helpful assistant.\n\nYou can use the thinking tool for complex responses.",
          "passthroughBinaryImages": true
        },
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "ea8207a4-f048-4a68-9971-eda5cddd0937",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -224,
        -1152
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "chatgpt-4o-latest",
          "cachedResultName": "chatgpt-4o-latest"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f6ea0142-c74d-4284-9b3b-661b6d2c6739",
      "name": "Format Response",
      "type": "n8n-nodes-base.set",
      "position": [
        656,
        -1296
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "372b5ab9-31f3-4761-8ef5-2aeb9161e611",
              "name": "aiResponse",
              "type": "string",
              "value": "={{ $('AI Agent').item.json.output }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "2d94293e-99b1-4df4-b2b7-ae5805c0dc24",
      "name": "Format Request",
      "type": "n8n-nodes-base.set",
      "position": [
        -608,
        -1872
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1d5ca98f-be4e-46a1-881b-031d5e0314ef",
              "name": "timeStamp",
              "type": "number",
              "value": "={{ $json.timestamp }}"
            },
            {
              "id": "e89cfb5e-c437-44c0-b7ce-f83066154682",
              "name": "messageText",
              "type": "string",
              "value": "={{ $json.messageText }}"
            },
            {
              "id": "7f8c5bf3-f04a-4105-a9bd-0d185e17adcc",
              "name": "userName",
              "type": "string",
              "value": "={{ $json.sourceName }}"
            },
            {
              "id": "4f777db3-52ee-4fc8-8e1d-570b4abc46b2",
              "name": "userAccount",
              "type": "string",
              "value": "={{ $json.envelope.sourceNumber }}"
            },
            {
              "id": "b62bed50-d711-4f6d-a029-977fa30a1a1e",
              "name": "userUuid",
              "type": "string",
              "value": "={{ $json.sourceUuid }}"
            },
            {
              "id": "ff0d9912-2841-4c16-84be-c6f5bb54d17d",
              "name": "botAccount",
              "type": "string",
              "value": "={{ $json.account }}"
            },
            {
              "id": "e1c9c26e-33f7-41c2-85c6-ab0c5fdc772d",
              "name": "attachments",
              "type": "array",
              "value": "={{ $json.attachments }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d5fabed2-27ad-44b2-8d32-45330fb14d92",
      "name": "Verify User",
      "type": "n8n-nodes-base.if",
      "notes": "We filter incoming messages. The AI will only ever handle messages sent by us. This mitigates the risk of our phone number being leaked. That being said, even if it was, spam messages would not be sent to signal itself. They would be sent to the default google messenger app. Signal has built in policies for rate limiting to prevent spam like this, and in practise it is unheard of. safety does not hurt tho",
      "position": [
        -416,
        -1872
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a64d5b94-0be8-4546-b433-4262b44e1b47",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.userUuid }}",
              "rightValue": "="
            },
            {
              "id": "f08b01d8-8599-4d17-bf83-8ff97dada182",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.userAccount }}",
              "rightValue": "="
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4a795aaf-0e62-405c-a138-b7b14f791c35",
      "name": "Stop Typing",
      "type": "n8n-nodes-signal-cli-rest-api.signal",
      "position": [
        400,
        -1296
      ],
      "parameters": {
        "operation": "stopTyping",
        "recipient": "={{ $('Verify User').item.json.userAccount }}"
      },
      "credentials": {
        "signalApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "58ccbb0f-861a-4850-a9f7-dc91600d265b",
      "name": "Signal Trigger",
      "type": "n8n-nodes-signal-cli-rest-api.signalTrigger",
      "position": [
        -832,
        -1872
      ],
      "parameters": {},
      "credentials": {
        "signalApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1
    },
    {
      "id": "50a2dfbf-82a2-4fc1-86fa-40bbbf92f03a",
      "name": "Mark Message as Read",
      "type": "n8n-nodes-signal-cli-rest-api.signal",
      "position": [
        -800,
        -1296
      ],
      "parameters": {
        "operation": "markAsRead",
        "recipient": "={{ $json.userAccount }}",
        "targetSentTimestamp": "={{ $json.timeStamp }}"
      },
      "credentials": {
        "signalApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d16e5bad-1bfa-44e6-be85-fdeb17256bec",
      "name": "Send Message",
      "type": "n8n-nodes-signal-cli-rest-api.signal",
      "notes": "AI Chatbot responds to the user. for safety reasons, this is hardcoded to our user account. It is unlikely this is necessary, but extra measures dont hurt.",
      "position": [
        880,
        -1296
      ],
      "parameters": {
        "message": "={{ $json.aiResponse }}",
        "operation": "sendMessage",
        "recipient": "="
      },
      "credentials": {
        "signalApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1
    },
    {
      "id": "7719621e-e675-47f5-8308-846525bc7c8a",
      "name": "Start Typing",
      "type": "n8n-nodes-signal-cli-rest-api.signal",
      "position": [
        -560,
        -1296
      ],
      "parameters": {
        "operation": "startTyping",
        "recipient": "={{ $('Verify User').item.json.userAccount }}"
      },
      "credentials": {
        "signalApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4a3ee570-8ccc-4d83-a3c3-50dc1ba3019f",
      "name": "Thinking",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        80,
        -1152
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "4f128f7e-1bfa-48a5-8a2e-74f037531e6e",
      "name": "Simple Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        -80,
        -1152
      ],
      "parameters": {
        "sessionIdType": "customKey",
        "contextWindowLength": 10
      },
      "typeVersion": 1.3
    },
    {
      "id": "b117ef73-7a4b-4587-bfc4-b9e4da947352",
      "name": "On form submission",
      "type": "n8n-nodes-base.formTrigger",
      "disabled": true,
      "position": [
        48,
        -1776
      ],
      "parameters": {
        "options": {},
        "formTitle": "Register Phone Number",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Phone Number",
              "placeholder": "+1234567890",
              "requiredField": true
            },
            {
              "fieldLabel": "Captcha Token",
              "placeholder": "signalcaptcha://signal-hcaptcha-short***********************",
              "requiredField": true
            }
          ]
        },
        "formDescription": "=Enter the phone-number your signal chat-bot will use here.\n\nVisit this web-page: https://signalcaptchas.org/registration/generate and complete the captcha challenge. Right-click on the open-signal button and copy the link address. Paste it into the second field"
      },
      "typeVersion": 2.3
    },
    {
      "id": "2732ce54-d82b-4ee6-bf34-a2f8942f4144",
      "name": "Form",
      "type": "n8n-nodes-base.form",
      "disabled": true,
      "position": [
        448,
        -1776
      ],
      "parameters": {
        "options": {
          "formTitle": "Account Verification",
          "formDescription": "If the account registration process has succeeded, your phone number should have received an SMS code. Please enter that here and click 'submit'"
        },
        "formFields": {
          "values": [
            {
              "fieldType": "number",
              "fieldLabel": "Verification Token",
              "placeholder": "123456",
              "requiredField": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "b1df1977-39cd-472a-981a-a16c367fe2d3",
      "name": "Verify Account",
      "type": "n8n-nodes-base.httpRequest",
      "disabled": true,
      "position": [
        656,
        -1776
      ],
      "parameters": {
        "url": "=http://signal-api:8080/v1/register/{{ $('On form submission').item.json['Phone Number'] }}/verify/{{ $json['Verification Token']}}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "e1a53ef0-caf9-4221-80b8-de146072c734",
      "name": "Register Account",
      "type": "n8n-nodes-base.httpRequest",
      "disabled": true,
      "position": [
        240,
        -1776
      ],
      "parameters": {
        "url": "=http://signal-api:8080/v1/register/{{ $json['Phone Number'] }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "captcha",
              "value": "={{ $json['Captcha Token'] }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "main-overview-sticky",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1568,
        -2048
      ],
      "parameters": {
        "width": 600,
        "height": 992,
        "content": "## How it works\n\nThis workflow creates a personal AI assistant that responds to your Signal messages. When you send a message, the bot verifies it's from you (blocking strangers), marks it as read, shows a typing indicator, processes your message with OpenAI, then responds naturally.\n\nThe bot maintains conversation context using a 10-message memory buffer and can use the thinking tool for complex queries. You control which AI model to use and can add custom tools for specialized tasks.\n\nRequires a self-hosted API endpoint and community nodes, only works with self-hosted n8n instances\n\n## Setup steps\n\n1. **Self-host Signal API**: Deploy [signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api) and install [n8n-nodes-signal](https://github.com/ZBlaZe/n8n-nodes-signal) community nodes\n2. **Register bot account**: Enable the registration nodes and complete the two-form process (captcha + SMS verification)\n3. **Configure credentials**: Add your Signal API endpoint and bot account number to n8n credentials\n4. **Obtain UUID**: Run a quick test by sending a signal message. Your device UUID can be obtained this way, in the output of the Signal trigger node.\n4. **Set user verification**: In the Verify User node, replace `\"=\"` with your Signal phone number and device UUID\n5. **Add OpenAI key**: Configure the OpenAI Chat Model node with your API credentials, or configure a different AI mode if preferred\n6. **Activate workflow**: Test by sending yourself a message"
      },
      "typeVersion": 1
    },
    {
      "id": "registration-section-sticky",
      "name": "Registration Section",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        -2032
      ],
      "parameters": {
        "color": 7,
        "width": 652,
        "height": 200,
        "content": "## Registration (One-time Setup)\n\nThese webforms guide the user  through registering your bot's Signal account. Disabled after initial setup. A separate phone-number for the bot itself is required, to be used as the bot's signal account number"
      },
      "typeVersion": 1
    },
    {
      "id": "intake-section-sticky",
      "name": "Message Intake",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -800,
        -2032
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 132,
        "content": "## Message Intake & Security\n\nReceives Signal messages, extracts user info, and verifies sender identity before processing."
      },
      "typeVersion": 1
    },
    {
      "id": "ai-section-sticky",
      "name": "AI Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -224,
        -1456
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 136,
        "content": "## AI Brain\n\nOpenAI processes messages with conversation memory and thinking capability. Choose your model and add custom tools here."
      },
      "typeVersion": 1
    },
    {
      "id": "response-section-sticky",
      "name": "Response Handling",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        -1488
      ],
      "parameters": {
        "color": 7,
        "width": 614,
        "content": "## Response Flow\n\nFormats AI output and sends reply to user with natural typing indicators."
      },
      "typeVersion": 1
    },
    {
      "id": "ux-section-sticky",
      "name": "UX Enhancements",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -784,
        -1472
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 144,
        "content": "## User Experience\n\nMarks messages as read and shows typing status for natural conversation feel."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Form": {
      "main": [
        [
          {
            "node": "Verify Account",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Stop Typing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Thinking": {
      "ai_tool": [
        [
          {
            "node": "AI Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Stop Typing": {
      "main": [
        [
          {
            "node": "Format Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify User": {
      "main": [
        [
          {
            "node": "Mark Message as Read",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Typing": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Format Request": {
      "main": [
        [
          {
            "node": "Verify User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Signal Trigger": {
      "main": [
        [
          {
            "node": "Format Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Response": {
      "main": [
        [
          {
            "node": "Send Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Register Account": {
      "main": [
        [
          {
            "node": "Form",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "On form submission": {
      "main": [
        [
          {
            "node": "Register Account",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Message as Read": {
      "main": [
        [
          {
            "node": "Start Typing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}