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:
Local connector matching
- finds connectors on the account whose tags overlap with the message tags and delivers the payload to each one.
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.
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:
{
"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. |
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:
- Contact reads
strandTriggerDepthfrom the inbound message'scontextand passes it astriggerDepthin the Strand trigger payload. - 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.strandTriggerDepthon the outbound message. - On the next round-trip, Strand checks the incoming
triggerDepth. If it exceeds 3, Strand rejects the trigger and returns an error message.
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)
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:
- Per-workflow rate limit: a maximum of 10 Contact triggers per workflow within a 60-second sliding window.
- Per-trigger fan-out cap: a single Contact event can trigger at most 50 workflows.
Prerequisites
There is no per-account toggle for the Strand integration in Contact settings. Triggering works once these conditions are met:
- 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. - 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.
- 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.
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
- The trigger is fire-and-forget. Contact does not block on Strand's response.
- If Strand does not respond in time, the trigger is silently dropped.
- Contact uses the same tags for both local connector matching and Strand triggering. There is no separate tag namespace.
Tendrl