Docs / Contact / integrations/strand-integration

Strand Integration

Contact can automatically trigger Strand workflows whenever a tagged message is processed. This enables event-driven automation: a sensor reading, a webhook, or a state change in Contact can kick off an arbitrarily complex workflow in Strand without any polling or manual intervention.

How It Works

When Contact processes a message that has context.tags, it performs two routing actions in parallel:

1

Local connector matching

- finds connectors on the account whose tags overlap with the message tags and delivers the payload to each one.

2

Strand workflow triggering

- sends an async HTTP POST to Strand's contact-integration trigger endpoint with the message tags and payload. Strand then matches those tags against workflows that have been exposed to the Contact integration layer and starts a run for each match.

The Strand trigger happens early in the flow-processing pipeline, before local connectors are dispatched. It is fire-and-forget: Contact does not wait for Strand to finish (or even respond) before continuing with local connector delivery. Errors from the Strand trigger are silently discarded.

code

Message with tags: ["alert", "temp-high"]
          |
          v
   Contact: ProcessMessage()
          |
          +---> [async] Trigger Strand workflows
          |         Strand matches tags -> starts workflow runs
          |
          +---> Find local connectors with matching tags
          |         Enqueue connector tasks
          |
          v
   Flow document tracks connector delivery

Trigger Payload

Contact sends the following payload to Strand's trigger endpoint:

json

{
  "accountId": "100",
  "flowId": "6789abcdef012345",
  "flowName": "Contact Flow",
  "executionId": "6789abcdef012345",
  "tags": ["alert", "temp-high"],
  "entityId": "100:us-east:entity:sensor-1",
  "entityName": "sensor-1",
  "messageId": "abcdef1234567890",
  "data": {
    "temperature": 95.2,
    "location": "Building A"
  },
  "timestamp": "2025-01-15T10:30:00Z",
  "triggerDepth": 0
}
Field Description
accountId The account number (as a string) that owns the entity.
flowId The flow document ID created for this message processing run.
tags The message's routing tags. Strand uses these to find matching workflows.
entityId Full resource path of the entity that sent the message.
entityName Short name of the sending entity.
messageId The unique identifier (hex format) of the stored message.
data The message's data payload -- your application data.
timestamp UTC RFC 3339 timestamp of when the trigger was created.
triggerDepth Loop-prevention counter. See Loop Prevention below.
Accessing data inside Strand

Inside Strand workflows, your original message data is available at payload.data; use {{ payload.data.temperature }} in node configurations (JSONPath: $.payload.data.temperature). Entity details are at payload.entity.name / payload.entity.id, and the triggering flow's metadata at payload.contactFlow.*.

Loop Prevention

Strand workflows can send messages back to Contact (for example, using an HTTP node that posts to POST /api/entities/message). If that return message also has tags, Contact will trigger Strand again, which could create an infinite loop.

Contact and Strand prevent this with a trigger depth counter:

  1. Contact reads strandTriggerDepth from the inbound message's context and passes it as triggerDepth in the Strand trigger payload.
  2. When a Strand workflow sends a message back to Contact with tags, the Strand worker increments the depth by 1 and includes it as context.strandTriggerDepth on the outbound message.
  3. On the next round-trip, Strand checks the incoming triggerDepth. If it exceeds 3, Strand rejects the trigger and returns an error message.
code

Depth 0: Entity -> Contact -> Strand        (allowed)
Depth 1: Strand -> Contact -> Strand         (allowed)
Depth 2: Strand -> Contact -> Strand         (allowed)
Depth 3: Strand -> Contact -> Strand         (allowed)
Depth 4: Strand -> Contact -> Strand         (REJECTED)
Warning

The strandTriggerDepth field is transient -- it is not persisted with the message. It exists only in the in-flight message context for the duration of the trigger chain. If you manually send a message via the API without setting strandTriggerDepth, the depth resets to 0.

Strand also applies additional rate limiting on top of depth checking:

Prerequisites

There is no per-account toggle for the Strand integration in Contact settings. Triggering works once these conditions are met:

  1. Strand is available on the platform. The integration is enabled at the system level by Tendrl. You can confirm availability from GET /api/flows/strand-integration/status (configured: true). If it is not configured, triggers are silently skipped.
  2. Your account has an active Strand plan. Strand triggering is gated by the account's Strand plan, which is provisioned by Tendrl. If your account has no Strand plan, contact Tendrl to add one.
  3. The target workflow is exposed in Strand with matching tags. On the Strand side, each workflow must be explicitly exposed to the Contact integration. A workflow fires only when its tag list contains every tag on the message (workflow tags ⊇ message tags): extra tags on the workflow are fine, but a message tag the workflow doesn't have blocks the match. A message tagged ["telemetry", "alert"] will not trigger a workflow tagged only ["alert"]. Keep message tags minimal and give workflows every tag they should respond to.
Expose the workflow first

Matching happens at message-arrival time. A message that arrives before the workflow is exposed (or before its tags match) creates no flow and is not retried after you fix the exposure; only messages sent afterwards trigger it. Expose and test the workflow before pointing live device traffic at it.

Once these are in place, triggering happens automatically: Contact fires the trigger for any processed message whose tags match an exposed workflow (see How It Works). No additional configuration is required inside Contact.

Execution Tracking

When a Strand trigger fires successfully, Contact records a "Strand Workflow" step on the flow. The step carries the trigger outcome: success, a summary message, and the list of triggered workflows ({ workflowId, workflowName, runId, tags }). The flow is then marked completed.

This is visible in the Contact dashboard under the flow detail view, alongside local connector step results. On the Strand side, each triggered run appears in the Runs history with trigger_source: contact_flow and a full step-by-step trace.

Architecture Notes