gairė

Gairė in n8n

Gairė works in n8n with no node to install. Each alert can start a workflow through n8n's Webhook trigger, and an HTTP Request node can read your watches and changes through Gairė's API.

Each alert into a workflow

Gairė posts each alert, signed, to an address of yours (Webhooks). In n8n that address is a Webhook trigger's. Gairė only posts to https:// addresses on the public internet: n8n Cloud's are; a self-hosted n8n needs a public address of its own.

  1. In n8n, make a new workflow and choose Import from File or paste the workflow below into it. It has a Webhook trigger and the three nodes that check the request came from Gairė.
  2. Open the Gairė webhook node and copy its Production URL. Leave its settings as they are: POST, respond Immediately, and the Raw Body option on, which the check needs.
  3. Save the workflow and turn it on (Active). The Production URL answers only while it is on.
  4. In Gairė, open Recipients, choose Webhook and paste the Production URL. Gairė shows the webhook's secret once: copy it.
  5. Gairė sends a six-digit code to the address straight away. In n8n, open Executions, open the newest one, and read the code in the Gairė webhook node's output, in body.message. Type it into Gairė within 10 minutes.
  6. In the Sign it again node, replace PASTE-YOUR-WEBHOOK-SECRET with the secret from step 4, and save.
  7. Add what you want done after Use the change here: a message, a row in a sheet, a ticket. Each change's fields are in body, as listed on the Webhooks page.

What the nodes do:

  • Read the body as sent takes the body exactly as Gairė sent it, and the X-Gaire-Timestamp and X-Gaire-Signature headers.
  • Sign it again makes the HMAC-SHA256 of the timestamp, a full stop and that body, with your secret.
  • From Gairė? goes on only when sha256= and that HMAC is the signature, and the request is less than 5 minutes old. Anything else stops there.
  • A change? goes on only for a change (event is detection), so the six-digit code's request and any other event stop there.

The Read the body as sent node's code:

const out = [];
for (let i = 0; i < $input.all().length; i++) {
  const item = $input.all()[i];
  const raw = (await this.helpers.getBinaryDataBuffer(i, 'data')).toString('utf8');
  out.push({ json: { timestamp: item.json.headers['x-gaire-timestamp'], signature: item.json.headers['x-gaire-signature'], raw, body: item.json.body } });
}
return out;

The workflow to import:

{
  "name": "Gairė alerts (signed webhook)",
  "nodes": [
    {
      "id": "a1",
      "name": "Gairė webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "5f0c1a3e-7d55-4a51-9b0e-2b1f4c9c0a11",
      "parameters": {
        "httpMethod": "POST",
        "path": "gaire",
        "options": {
          "rawBody": true
        }
      }
    },
    {
      "id": "a2",
      "name": "Read the body as sent",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ],
      "parameters": {
        "jsCode": "const out = [];\nfor (let i = 0; i < $input.all().length; i++) {\n  const item = $input.all()[i];\n  const raw = (await this.helpers.getBinaryDataBuffer(i, 'data')).toString('utf8');\n  out.push({ json: { timestamp: item.json.headers['x-gaire-timestamp'], signature: item.json.headers['x-gaire-signature'], raw, body: item.json.body } });\n}\nreturn out;"
      }
    },
    {
      "id": "a3",
      "name": "Sign it again",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 1,
      "position": [
        440,
        0
      ],
      "parameters": {
        "action": "hmac",
        "type": "SHA256",
        "value": "={{ $json.timestamp }}.{{ $json.raw }}",
        "dataPropertyName": "expected",
        "secret": "PASTE-YOUR-WEBHOOK-SECRET",
        "encoding": "hex"
      }
    },
    {
      "id": "a4",
      "name": "From Gairė?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        660,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c1",
              "leftValue": "=sha256={{ $json.expected }}",
              "rightValue": "={{ $json.signature }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "c2",
              "leftValue": "={{ Math.abs(Date.now() / 1000 - Number($json.timestamp)) }}",
              "rightValue": 300,
              "operator": {
                "type": "number",
                "operation": "lt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "a7",
      "name": "A change?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        880,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c3",
              "leftValue": "={{ $json.body.event }}",
              "rightValue": "detection",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "a8",
      "name": "Use the change here",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1100,
        -100
      ],
      "parameters": {}
    }
  ],
  "connections": {
    "Gairė webhook": {
      "main": [
        [
          {
            "node": "Read the body as sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the body as sent": {
      "main": [
        [
          {
            "node": "Sign it again",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sign it again": {
      "main": [
        [
          {
            "node": "From Gairė?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "From Gairė?": {
      "main": [
        [
          {
            "node": "A change?",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "A change?": {
      "main": [
        [
          {
            "node": "Use the change here",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}

Your watches and changes in a workflow

An HTTP Request node can call Gairė's API, signed in with n8n's own OAuth2 credential. You sign in to Gairė once, and n8n keeps the sign-in fresh.

  1. In n8n, open Credentials, choose Create credential and pick OAuth2 API. Copy the OAuth Redirect URL it shows.
  2. Register n8n with Gairė once, with that address. The answer's client_id is n8n's:
curl -X POST https://gaire.lt/oauth/register \
  -H 'Content-Type: application/json' \
  -d '{"client_name": "n8n", "redirect_uris": ["<your OAuth Redirect URL>"]}'
  1. Fill in the credential:
    • Grant Type: PKCE
    • Authorization URL: https://gaire.lt/oauth/authorize
    • Access Token URL: https://gaire.lt/oauth/token
    • Client ID: the client_id from step 2
    • Client Secret: any word, such as none. n8n asks for one; Gairė's clients have none, and it is not read.
    • Scope: gaire:read to read, or gaire:read gaire:write also to subscribe an address
    • Auth URI Query Parameters and Authentication: leave as they are
  2. Choose Connect my account. Gairė opens: sign in, see that n8n is asking and for what, and say yes. The credential then shows it is connected.
  3. Add an HTTP Request node: method GET, URL https://gaire.lt/api/v1/changes?limit=5, Authentication: Generic Credential Type, then OAuth2 API and the credential from step 3. Run it: it gives your five most recent changes.

The other endpoints work the same way: /api/v1/me, /api/v1/watches and /api/v1/hooks, each on the API page.

Without the six-digit code

With gaire:write, the workflow can subscribe its own Webhook trigger instead of step 4 of the first part. An HTTP Request node, POST to https://gaire.lt/api/v1/hooks with the same credential, and this JSON body:

{ "url": "<your Webhook node's Production URL>" }

The answer's secret goes in the Sign it again node. Such a subscription needs no code, does not take one of your recipients, and stops when n8n is disconnected from Gairė.

Questions: maksim@gaire.lt