Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.spojit.com/llms.txt

Use this file to discover all available pages before exploring further.

Every workflow starts with a Trigger node. It defines how and when the workflow runs. Select the trigger type in the properties panel when you click on the trigger node.

Trigger types

TypeDescription
ManualRun the workflow on demand by clicking Run in the toolbar
WebhookRun when an external HTTP request hits the workflow’s webhook URL — supports Spojit’s own scheme plus Shopify, GitHub, Slack, and custom HMAC verification
ScheduleRun on a cron schedule (e.g., every hour, daily at 9 AM)
EmailRun when a new email arrives in a connected mailbox (Gmail or Outlook)

Manual

No additional configuration needed. Click Run in the designer toolbar to execute the workflow. The request body you pass (if any) is available as the trigger output for downstream nodes.

Webhook

A webhook trigger fires when an external HTTP service POSTs to its unique URL. Spojit verifies the request’s HMAC signature against a webhook signing connection that you create once per upstream app and reuse across as many workflows as you like.

How signing connections work

Each webhook trigger references a Connection that owns:
  • A scheme — one of Spojit, Shopify, GitHub, Slack, or Custom. The scheme determines which header carries the signature, what gets signed, and how it’s encoded.
  • One or two signing secrets. Two enables zero-downtime rotation; both verify simultaneously during the rotation window.
Multiple workflows can share the same connection. This matches how providers like Shopify, GitHub, and Slack issue one signing secret per app that signs every webhook from that app — rotating the secret upstream means updating exactly one connection in Spojit, and every workflow keeps working.

Setup

  1. Select the trigger node, set Trigger Type to Webhook.
  2. Click + next to the Signing connection picker.
  3. Pick a connector tile:
    • Spojit — we generate the secret. Pick this when you control both sides of the integration (your own backend, scripts, internal tools).
    • Shopify / GitHub / Slack — paste the signing secret from your provider’s app settings.
    • Custom — for any provider we don’t have a preset for; you configure header, encoding, algorithm, and payload shape.
  4. Name the connection (e.g. “Shopify production”), provide the secret if applicable, and Create.
  5. The connection is auto-selected. Copy the Webhook URL and paste it into your provider’s webhook configuration.
Manage existing connections (rotate secrets, see which workflows use them, delete) from Connections → My Connections — click Configure on a webhook row to open its detail page.

Choosing a scheme

SchemeHeaderSigned payloadEncodingPick when
SpojitX-Spojit-Signature: t=<ts>,v1=<hex><unix-ts>.<body>hexYour own backend / scripts / tools
ShopifyX-Shopify-Hmac-Sha256raw bodybase64Inbound from a Shopify app
GitHubX-Hub-Signature-256: sha256=<hex>raw bodyhexInbound from a GitHub App or repo webhook
SlackX-Slack-Signature: v0=<hex> (+ X-Slack-Request-Timestamp)v0:<ts>:<body>hexInbound from a Slack app’s Events API
CustomconfigurableconfigurableconfigurableAnything we don’t have a preset for
All schemes use HMAC-SHA256 by default. The Custom scheme additionally allows SHA-1 (weak — only for legacy providers that require it) and SHA-512.

Provider presets

Spojit

We generate a whsec_… secret on connection create and show it once. Sign every request like this:
  • Compute Unix timestamp in seconds
  • HMAC-SHA256 of <timestamp>.<raw-body> using your secret
  • Hex-encode the result
  • Send as header: X-Spojit-Signature: t=<timestamp>,v1=<hex>
  • Optional: X-Spojit-Event-Id: <unique-id> for deduplication.
Timestamp tolerance: 5 minutes either direction. Older requests are rejected as replays.

Shopify

In your Shopify admin: Settings → Notifications → Webhooks. Each webhook subscription has a signing secret you can reveal — paste it when creating the connection. Spojit verifies X-Shopify-Hmac-Sha256 (base64-encoded HMAC-SHA256 of the raw body). Deduplication uses Shopify’s X-Shopify-Webhook-Id header. Provider docs: Shopify webhooks configuration.

GitHub

In GitHub: when creating the webhook on a repository or GitHub App, set a Secret value. Paste the same value when creating the Spojit connection. Spojit verifies X-Hub-Signature-256: sha256=<hex>. Deduplication uses GitHub’s X-GitHub-Delivery header. Provider docs: Validating webhook deliveries.

Slack

In your Slack app: Basic Information → App Credentials → Signing Secret. Paste it when creating the Spojit connection. Spojit verifies X-Slack-Signature: v0=<hex> over v0:<ts>:<body>, with the timestamp in X-Slack-Request-Timestamp (5-minute skew). Deduplication uses Slack’s X-Slack-Event-Id header. URL verification: Slack sends a one-time url_verification event when you save the webhook URL. Spojit handles this automatically — verifies the HMAC, then echoes the challenge field as text/plain. No workflow run is dispatched for verification events. Provider docs: Verifying requests from Slack.

Custom

For providers without a preset, configure each axis explicitly:
FieldDescription
Signature header nameThe HTTP header carrying the HMAC (e.g. X-Vendor-Signature).
AlgorithmSHA-256 (default), SHA-1 (weak — legacy only), SHA-512.
Encodinghex or base64.
Value prefixOptional literal string stripped before comparison (e.g. sha256=).
Signed payloadRaw body (sign body verbatim) or Timestamp.body (Stripe-style — requires a timestamp header).
Timestamp header / skewRequired when payload includes a timestamp.
Event ID headerOptional — used for deduplication.

Example: Postman

To test your webhook from Postman: Request:
  • Method: POST
  • URL: your webhook URL (from the trigger properties panel)
  • Headers:
    • Content-Type: application/json
    • X-Spojit-Signature: generated by the pre-request script below
    • X-Spojit-Event-Id: any unique string (optional — used for deduplication)
  • Body (raw JSON):
{
  "event": "order.created",
  "data": {
    "orderId": "ORD-12345",
    "amount": 99.99
  }
}
Pre-request Script (paste in Postman’s Scripts → Pre-request tab):
const secret = "whsec_YOUR_SECRET_HERE";
const timestamp = Math.floor(Date.now() / 1000).toString();
const body = pm.request.body.raw;
const payload = timestamp + "." + body;
const signature = CryptoJS.HmacSHA256(payload, secret)
  .toString(CryptoJS.enc.Hex);

pm.request.headers.upsert({
  key: "X-Spojit-Signature",
  value: "t=" + timestamp + ",v1=" + signature
});
Replace whsec_YOUR_SECRET_HERE with your actual signing secret.

Example: Node.js

import crypto from "node:crypto";

const secret = "whsec_YOUR_SECRET_HERE";
const body = JSON.stringify({ event: "order.created", data: { orderId: "ORD-12345" } });
const timestamp = Math.floor(Date.now() / 1000);
const signature = crypto
  .createHmac("sha256", secret)
  .update(`${timestamp}.${body}`)
  .digest("hex");

const response = await fetch("https://your-webhook-url", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Spojit-Signature": `t=${timestamp},v1=${signature}`,
    "X-Spojit-Event-Id": "evt_unique_123",
  },
  body,
});

console.log(response.status); // 202

Example: Python

import hmac, hashlib, time, json, requests

secret = "whsec_YOUR_SECRET_HERE"
body = json.dumps({"event": "order.created", "data": {"orderId": "ORD-12345"}})
timestamp = str(int(time.time()))
signature = hmac.new(
    secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256
).hexdigest()

response = requests.post(
    "https://your-webhook-url",
    headers={
        "Content-Type": "application/json",
        "X-Spojit-Signature": f"t={timestamp},v1={signature}",
        "X-Spojit-Event-Id": "evt_unique_456",
    },
    data=body,
)

print(response.status_code)  # 202

Response codes

CodeMeaning
202Accepted — workflow run dispatched. Response includes executionId and status.
401Signature missing, invalid, or timestamp outside the 5-minute tolerance window.
404Trigger not found or inactive.
409Duplicate event — a request with the same event-id header was already processed. Response includes the original executionId.
402Insufficient credits.
403Not authorized to run this workflow.
500Internal error.

Deduplication

Deduplication is opt-in via an event-id header. When the header is present, a second request with the same id on the same webhook returns 409 with the original executionId instead of running the workflow again. When it’s absent, every request fires a fresh execution. The header depends on the connection type:
Connection typeEvent-id header
Spojit WebhookX-Spojit-Event-Id (optional — set per logical event if you want retries deduped)
Shopify WebhookX-Shopify-Webhook-Id (Shopify sends automatically)
GitHub WebhookX-GitHub-Delivery (GitHub sends automatically)
Slack Webhookevent_id field in the request body (Slack sends automatically)
Custom WebhookThe header name you configured under Event ID header when creating the connection

Secret rotation

Each connection can hold up to 2 active signing secrets at a time, enabling zero-downtime rotation. Because rotation lives on the connection, all triggers using that connection rotate atomically — there’s never a window where some workflows accept the old secret and others don’t. Open the connection’s detail page (Connections → My Connections → Configure on the row), then:
  1. Click Rotate / add secret to create a second secret.
  2. Spojit scheme: copy the newly generated secret (shown once). Other schemes: rotate the secret upstream (in Shopify / GitHub / Slack admin, etc.), then paste the new value here.
  3. Both secrets verify simultaneously. Check the Last used timestamp on each — once the new secret shows recent activity, you’ve fully cut over.
  4. Delete the old secret.
The detail page also shows every workflow whose webhook trigger references this connection — handy for confirming what’ll be affected before you rotate. Spojit verifies incoming requests against all active secrets on the connection; the first match wins.

Webhook payload

The full HTTP request body is passed as the trigger output to downstream nodes. If the body is valid JSON, it’s parsed automatically. Otherwise, it’s available as a raw string under { raw: "..." }.

Polling execution status

When your webhook returns 202 Accepted (either async mode, or sync mode that hit its timeout), you’ll get back an executionId. You can poll the status endpoint to find out where the run is and retrieve the output once it’s done. Endpoint:
GET /api/executions/{executionId}/status
Authentication: same HMAC scheme as webhook ingress — reuse your webhook’s signing secret. No separate credentials. The signed payload is {timestamp}.{executionId}.
X-Spojit-Signature: t=<unix-timestamp>,v1=<hex-hmac-sha256>
Response (200 OK):
{
  "executionId": "cc74c932-59a8-4838-aef7-744b7a53637f",
  "status": "RUNNING",
  "currentStep": {
    "id": "connector-abc123",
    "label": "AI Agent",
    "status": "RUNNING"
  },
  "stepsCompleted": 1,
  "totalSteps": 3,
  "output": null,
  "error": null
}
Fields:
FieldDescription
statusPENDING, RUNNING, WAITING_APPROVAL, COMPLETED, FAILED, or CANCELLED
currentStepThe step currently executing (or waiting, e.g. for approval). null when the run has finished.
stepsCompletedCount of steps that have reached COMPLETED. Grows as the run progresses.
totalStepsTotal work-producing steps in the workflow. Only populated for linear workflows. Returns null if the graph contains any Condition, Loop, or Parallel node (actual path length is dynamic).
outputThe workflow’s output, populated once status === 'COMPLETED'. When the workflow ends with a Response node, this mirrors its { statusCode, headers, body } shape.
errorError message if status === 'FAILED', otherwise null.
Response codes:
CodeMeaning
200Status retrieved
401Signature missing, invalid, expired (>5 min), or the originating trigger has no signing secrets
404Execution not found

Example: Node.js polling

import crypto from "node:crypto";

const secret = "whsec_YOUR_SECRET";
const executionId = "cc74c932-59a8-4838-aef7-744b7a53637f";

async function pollStatus() {
  const timestamp = Math.floor(Date.now() / 1000);
  const signature = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${executionId}`)
    .digest("hex");

  const response = await fetch(
    `https://your-spojit-host/api/executions/${executionId}/status`,
    {
      headers: {
        "X-Spojit-Signature": `t=${timestamp},v1=${signature}`,
      },
    },
  );

  return response.json();
}

// Poll until terminal status
while (true) {
  const status = await pollStatus();
  console.log(status.status, status.currentStep?.label ?? "—");
  if (["COMPLETED", "FAILED", "CANCELLED"].includes(status.status)) {
    console.log("Output:", status.output);
    break;
  }
  await new Promise((r) => setTimeout(r, 2000));
}

Example: Python polling

import hmac, hashlib, time, requests

secret = "whsec_YOUR_SECRET"
execution_id = "cc74c932-59a8-4838-aef7-744b7a53637f"

def poll_status():
    timestamp = str(int(time.time()))
    signature = hmac.new(
        secret.encode(),
        f"{timestamp}.{execution_id}".encode(),
        hashlib.sha256,
    ).hexdigest()

    response = requests.get(
        f"https://your-spojit-host/api/executions/{execution_id}/status",
        headers={
            "X-Spojit-Signature": f"t={timestamp},v1={signature}",
        },
    )
    return response.json()

while True:
    status = poll_status()
    print(status["status"], (status.get("currentStep") or {}).get("label", "—"))
    if status["status"] in ("COMPLETED", "FAILED", "CANCELLED"):
        print("Output:", status["output"])
        break
    time.sleep(2)

Example: Postman

Same pre-request script as the webhook call, but with the payload changed to sign {timestamp}.{executionId}:
const secret = "whsec_YOUR_SECRET";
const url = pm.request.url.toString();
const match = url.match(/\/executions\/([^/]+)\/status/);
const executionId = match ? match[1] : "";

const timestamp = Math.floor(Date.now() / 1000).toString();
const payload = timestamp + "." + executionId;
const signature = CryptoJS.HmacSHA256(payload, secret)
  .toString(CryptoJS.enc.Hex);

pm.request.headers.upsert({
  key: "X-Spojit-Signature",
  value: "t=" + timestamp + ",v1=" + signature
});
  • Start with 1–2 second intervals for short-running workflows
  • Back off to 5–10 seconds for workflows that routinely take minutes
  • Always stop polling once status is COMPLETED, FAILED, or CANCELLED — these are terminal

Schedule

Run a workflow automatically on a cron schedule. Each firing produces an independent workflow execution, identical in every way to a manually triggered run.

Setup

  1. Select the trigger node in the designer
  2. Set Trigger Type to Schedule
  3. Click Add Schedule
  4. Edit the Cron expression (5-field Unix cron — e.g., 0 9 * * 1-5 for 9 AM weekdays)
  5. Pick a Timezone (IANA, e.g. Australia/Sydney, America/New_York)
  6. Leave Active on to have it fire; toggle off to pause
  7. Click Save workflow — the schedule begins firing on the next matching time
Changes only apply on Save. Editing the cron or timezone field does not affect the running schedule until you save. The Next fires preview shows the three upcoming firings based on the currently saved configuration, so you can always see what’s actually going to run.

Multiple schedules

A single trigger node can hold any number of schedules. Use this to model real-world timing needs such as:
  • Business hours vs. weekends — one schedule for 0 9 * * 1-5 (weekdays 9 AM) and another for 0 12 * * 0,6 (weekends noon)
  • Peaks and off-peaks — run every 15 minutes during business hours, every hour overnight
  • Multi-region timing — one schedule in America/New_York, another in Europe/London, both hitting the same workflow
Each schedule is independent — you can pause one and leave the other active, or delete one without affecting the others.

Cron format

Schedules use standard 5-field Unix cron:
┌───────────── minute (0-59)
│ ┌─────────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌─────── month (1-12)
│ │ │ │ ┌───── day of week (0-6, Sun=0)
│ │ │ │ │
* * * * *
ExpressionMeaning
0 * * * *Every hour on the hour
*/15 * * * *Every 15 minutes
0 9 * * *Every day at 9 AM
0 9 * * 1-59 AM on weekdays
0 0 1 * *Midnight on the 1st of every month
0 9 * * 19 AM every Monday
The properties panel shows a human-readable description under the cron input (e.g. “At 09:00 AM, Monday through Friday”) so you can verify your expression before saving.

Timezones

Every schedule has an IANA timezone (e.g. Australia/Sydney, Europe/London, UTC). The cron expression is evaluated in that timezone, which means daylight savings transitions are handled correctly — a 0 9 * * * schedule in America/New_York fires at 9 AM local time year-round, not at a fixed UTC offset.

Pausing a schedule

Toggle the Active switch off and save the workflow. The schedule is retained in the workflow’s configuration (so you can re-enable it later) but won’t fire while paused. This is reversible without losing the cron/timezone configuration.

Tips

  • The Next fires preview shows the saved schedule, not your draft edits — trustworthy readout of what’s actually going to run
  • You can combine a Schedule trigger with a Manual or Webhook trigger on the same workflow. Each is an independent entrypoint that converges on the same downstream logic.
  • Keep the cron on conservative intervals during development (e.g. every 10 min) to validate behavior before switching to production cadence

Email

Run a workflow automatically when a new email arrives in a connected Gmail or Outlook mailbox. The workflow receives the parsed message — sender, subject, body, attachment metadata — and can act on it (extract data from a PDF order form, write to your ERP, notify a Slack channel, etc.).

How it works

You connect a mailbox to Spojit via OAuth (no password sharing — the OAuth grant is read-only by default). Spojit polls that mailbox on a schedule you choose and fires a workflow execution for every new message that matches your filters. The mail itself never travels through any third party — Spojit reads it directly from your provider (Google or Microsoft) using the OAuth token you granted. Instead of connecting your everyday inbox, create a dedicated mailbox in your workspace just for the workflow — for example, orders-automation@acme.com. Then set up a forwarding rule (or share the address with senders) so only the relevant emails land there. Benefits:
  • Clear scope — Spojit only ever sees the dedicated mailbox, not your personal inbox
  • Easy to revoke — disconnect the mailbox in Spojit or delete the mailbox in your workspace; either kills access
  • Easy to audit — IT can see exactly what’s flowing through the dedicated address

Setup

  1. Connect the mailbox in the Connections page:
    • Click Add connection → pick Gmail (trigger) or Outlook (trigger)
    • Choose a permission level:
      • Read only — Spojit can only read messages
      • Read + modify — Spojit can also mark messages as read or move them after processing (needed for the optional “After processing” actions below)
    • Sign in with the account that owns the mailbox
    • The connection appears in your list with the granted scopes visible
  2. Add the trigger in the workflow designer:
    • Drop a Trigger node, set Trigger Type to Email
    • Click Add mailbox
    • Pick the connection from the dropdown
    • Pick the folder/label to watch (e.g., INBOX, or a custom label like Spojit/Orders)
    • Set the poll interval (1–60 minutes; default 2)
    • Optional: filter by sender domain or subject regex
    • Optional: pick what to do with the message after Spojit processes it
  3. Click Save workflow — Spojit starts polling on the next cycle

Filters

Two optional filters narrow what triggers a run:
  • From allowlist — comma-separated patterns. @acme.com matches any sender at acme.com; orders@vendor.com matches the exact address. Multiple patterns are OR’d.
  • Subject regex — a JavaScript-flavor regular expression. The message fires only if the subject matches.
If both filters are set, both must pass.

After processing

By default Spojit doesn’t touch the message — it stays in your folder, in whatever read state it was. If you want, you can ask Spojit to:
  • Mark as read — useful for visual confirmation that Spojit picked it up
  • Move to label / folder — pick a destination; in Gmail the label is added (other labels stay), in Outlook the message moves out of the source folder
Both options require a Read + modify connection — pick one when you create the connection if you plan to use these. Read-only connections will have the modify options disabled in the trigger panel.

Multiple mailboxes per workflow

A single workflow can watch multiple mailboxes — useful for things like “watch our US orders mailbox AND our EU orders mailbox, both run the same processing logic.” Add as many as you need; each fires the workflow independently.

Multiple workflows watching the same mailbox

Different workflows can watch the same mailbox + folder. Every matching message fires every matching workflow — independently and in parallel. Common pattern: one workflow processes the order into your ERP, another notifies sales on Slack — both fire on each incoming email. A few important behavior notes for this pattern:
  • No “first one wins” — each workflow gets its own copy of the email, runs its own logic
  • After processing actions don’t hide messages from siblings — even if Workflow A moves the message to a Processed folder, Workflow B still sees and processes it within the same poll cycle. You won’t miss a workflow because of another workflow’s cleanup action.
  • Conflicting after-processing actions apply in order — if A says “mark read” and B says “move to folder X”, both happen. In Gmail, labels accumulate; in Outlook, the move wins.
The one limit: within a single workflow, you can’t add two EMAIL triggers with identical settings (same connection + folder + filters) — that would just fire the workflow twice for every message, which is never what you want. Cross-workflow duplicates are fine and intentional.

What Spojit can and can’t see

  • Can see: messages in the folder you select. Spojit’s code is committed to only querying that folder, even though the OAuth scope technically grants whole-mailbox access (this is how Google/Microsoft OAuth scopes work — folder-level scoping isn’t an option from the provider).
  • Can’t do (read-only connections): mark messages as read, move messages, delete messages
  • Can do with Read + modify (only if you picked it at connection setup): mark as read, move/label — never delete
  • Doesn’t store: full message bodies. Bodies and attachments are passed into the workflow execution and retained per the workflow execution’s normal retention policy. They are not stored in any separate Spojit-owned mailbox.

Disconnecting

In the Connections page, click Remove on the email connection. Spojit deletes the OAuth token and stops polling immediately. To revoke at the provider side as well (defense in depth):

Tips

  • Start with a dedicated mailbox — much easier story for IT and for limiting blast radius
  • Use Read only by default; only upgrade to Read + modify if you actually want Spojit to mark or move messages
  • The poll interval doesn’t need to be aggressive — 2–5 minutes is fine for almost every order-processing use case; 1-minute polls aren’t dramatically faster end-to-end given queue + provider latency
  • Pair a dedicated mailbox with a forwarding rule from your main inbox to filter just the messages you want Spojit to act on
  • For testing, send yourself an email matching your filters and watch the Recent activity panel to confirm it landed

Output

The trigger node produces an output that downstream nodes can reference:
  • Manual: The input data passed when clicking Run (if any)
  • Webhook: The parsed HTTP request body
  • Schedule: { scheduledAt } — the ISO-8601 timestamp of the firing that started the run
  • Email: { from, to, cc, subject, textBody, htmlBody, headers, receivedAt, conversationId, attachments[] }. Each attachment is a reference ({ provider, providerMessageId, attachmentId, filename, mimeType, size }) — the bytes are fetched on demand by downstream nodes that need them.

Tips

  • Start with Manual triggers during development, then switch to Webhook, Schedule, or Email when you’re ready for production
  • A workflow can have multiple triggers (e.g., Manual + Webhook, or Schedule + Email) — each is an independent entrypoint
  • Use the Active toggle to temporarily pause any trigger without deleting it or losing the configuration