How to Poll a REST API for Changes and Detect Drift Against MongoDB

Build a Spojit workflow that fetches a configuration or inventory endpoint on a schedule, compares it against the last snapshot stored in MongoDB, and alerts Slack with the exact fields that changed whenever drift is detected.

What This Integration Does

Many systems expose a REST endpoint that returns their current state: a feature-flag service, a DNS or firewall config API, a load-balancer inventory, a price list, or a fleet of devices. When that state changes without anyone telling you, you have configuration drift. This workflow turns Spojit into a lightweight drift detector. On a fixed schedule it pulls the endpoint, diffs the fresh response against the snapshot it saved last time, and posts a Slack message listing every field that was added, removed, or changed. Nothing changes silently anymore.

A Schedule trigger fires on a cron expression. A Connector node on the http connector fetches the live state. A Connector node on the mongodb connector reads the previous snapshot, and the json connector's diff tool computes the leaf-level differences. A Condition node checks whether the difference count is greater than zero. When there is drift, the workflow upserts the new snapshot back into MongoDB and sends a Slack alert; when there is none, it ends quietly. Each run leaves exactly one snapshot document behind, so every run compares against the immediately preceding state and re-runs are idempotent.

Prerequisites

  • A MongoDB connection in Spojit (added under Connections -> Add connection) pointing at the database where you want to keep snapshots. See the MongoDB connector article for setup.
  • A Slack connection with permission to post to the channel you want alerts in, plus the target channel ID (for example C0123ABCD). See the Slack connector article.
  • The REST endpoint URL you want to monitor and any auth header it needs (an API key or bearer token). The built-in http and json connectors need no connection or credentials.
  • A MongoDB collection name reserved for snapshots, for example config_snapshots, and a stable key value that identifies this particular endpoint (so you can monitor several endpoints in one collection).

Step 1: Add a Schedule trigger

Start a new workflow and set the Trigger node type to Schedule. Enter a 5-field cron expression and an IANA timezone. To poll every 15 minutes use */15 * * * *; to check once each weekday morning use 0 9 * * 1-5 with a timezone such as Australia/Sydney. The trigger output is { scheduledAt }, which you can reference downstream as {{ trigger.scheduledAt }} to timestamp your snapshot. A single trigger can hold multiple schedules if you want more than one cadence.

Step 2: Fetch the live state with the http connector

Add a Connector node in Direct mode on the http connector and pick the http-get tool. Set url to the endpoint you are monitoring, for example https://api.example.com/v1/config. If the API needs authentication, add a headers object such as:

{
  "Authorization": "Bearer YOUR_API_TOKEN",
  "Accept": "application/json"
}

Direct mode is deterministic and costs no AI credits, which is exactly what you want for a predictable single GET. Name the output variable live. The parsed response body is then available as {{ live.data }}. If your endpoint wraps the meaningful state inside an envelope, you can narrow it later with the json connector's get or pick tool so you only diff the part that matters.

Step 3: Read the previous snapshot from MongoDB

Add a Connector node in Direct mode on the mongodb connector and pick the find-documents tool. Set collection to config_snapshots, set a filter that matches this endpoint's snapshot, and set limit to 1:

{
  "collection": "config_snapshots",
  "filter": { "key": "example-config" },
  "limit": 1
}

Name the output variable previous. On the very first run no document exists yet, so the result is empty. Reference the stored state as {{ previous.documents.0.state }} assuming you save the captured body under a state field (Step 5 does exactly that). An empty previous snapshot will diff as a long list of "added" fields on the first run, which is expected.

Step 4: Diff the new state against the old with the json connector

Add a Connector node in Direct mode on the json connector and pick the diff tool. Map the two objects:

{
  "obj1": "{{ previous.documents.0.state }}",
  "obj2": "{{ live.data }}"
}

Name the output variable drift. The diff tool returns a flat, leaf-level comparison: {{ drift.differences }} is an array where each entry has a path (the dotted location of the diverging field), a type of added, removed, or changed, and oldValue / newValue as appropriate. {{ drift.count }} is the number of differences, and a count of 0 means the two states are identical. Because the comparison is leaf-level, reordered keys at the same path do not register as drift, only real value changes do.

Step 5: Branch on drift and upsert the new snapshot

Add a Condition node that checks whether {{ drift.count }} is greater than 0. Wire the false branch to nothing (the run ends with no snapshot churn and no alert when state is stable). On the true branch, first add a Connector node in Direct mode on the mongodb connector with the update-documents tool to save the fresh state. Set upsert to true so the document is created on the first run and overwritten on later runs:

{
  "collection": "config_snapshots",
  "filter": { "key": "example-config" },
  "update": {
    "$set": {
      "state": "{{ live.data }}",
      "updatedAt": "{{ trigger.scheduledAt }}"
    }
  },
  "upsert": true
}

The tool returns matchedCount, modifiedCount, and upsertedCount, so you can confirm the snapshot was written. Because the filter targets a single key, exactly one snapshot per monitored endpoint is kept, and the next run diffs against this freshly written state.

Step 6: Format the changed fields with a Transform node

A raw differences array is noisy in chat, so add a Transform node to turn {{ drift.differences }} into readable lines. Build one string per change using the path, oldValue, and newValue of each entry, for example a line shaped like config.timeout: 30 -> 60 for a changed field, + config.newFlag for an added field, and - config.oldFlag for a removed field. Name the output variable summary and reference the joined text as {{ summary.text }}. Keeping the formatting in a Transform node means the Slack step stays a single clean template. If you prefer a more polished, plain-language summary, you can ask Miraxa, the intelligent layer across your automation, to scaffold this Transform for you and then fine-tune the wording in the properties panel.

Step 7: Alert Slack with the changed fields

Still on the true branch, add a Connector node in Direct mode on the slack connector and pick the send-message tool. Set channel to your target channel ID and build the text from the variables you collected:

{
  "channel": "C0123ABCD",
  "text": "Config drift detected on example-config ({{ drift.count }} change(s)) at {{ trigger.scheduledAt }}:\n{{ summary.text }}"
}

When the run finishes, the channel shows exactly which fields drifted, with old and new values, and MongoDB holds the new baseline ready for the next comparison. That closes the loop: detect, alert, and re-baseline in one pass.

Tips

  • Use one shared snapshot collection with a distinct key per endpoint, then clone this workflow for each system you monitor. The diff and upsert filters only need the right key to keep them isolated.
  • If only part of the payload matters (say a settings block), narrow both sides before the diff with the json connector's pick or get tool so volatile fields like timestamps or request IDs do not trigger false drift.
  • For very large responses, the diff count alone is a cheap signal: branch on {{ drift.count }} first and only build the detailed Slack summary when it is non-zero, which keeps quiet runs fast.
  • Use {{ trigger.scheduledAt }} as the snapshot updatedAt value so each baseline carries the run time, making the execution history easy to correlate with what changed.

Common Pitfalls

  • First-run noise: with no prior snapshot, the diff reports every field as added. Run it once manually to seed the baseline, or accept one large initial alert.
  • Endpoints that include rotating values (a generatedAt timestamp, a nonce, a server-side request ID) will diff on every poll. Strip those fields with the json connector before diffing, or the workflow will alert constantly.
  • Forgetting upsert: true on update-documents means the first run matches nothing and never writes a baseline, so every subsequent run keeps diffing against an empty snapshot. Always set it to true.
  • Cron runs in the timezone you choose on the Schedule trigger, not the API's timezone. Pick the IANA zone deliberately so a 0 9 * * 1-5 schedule fires when you expect.
  • If the http-get call fails or returns an error body instead of the real state, that error payload becomes the new "live" state and will diff loudly. Consider a Condition guard on the HTTP status before diffing.

Testing

Before scheduling it, validate the logic on a small scope. Point http-get at a stable test endpoint or a JSON fixture, run the workflow once with the Run button to seed the baseline in config_snapshots, and confirm no Slack message is sent on an immediate second run (the count should be 0). Then deliberately change one field at the source, run again, and check that Slack shows just that field and that MongoDB now holds the updated snapshot. Review each step's output in the execution history to confirm {{ drift.differences }} matches what you changed. Once the diff, upsert, and alert behave correctly on a single endpoint, enable the Schedule trigger and roll it out to your other endpoints.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.