Docs / Surface / contact-integration
Contact Integration
Surface integrates with Contact so you can screen message payloads for threats and trigger Strand workflows based on scan results.
How it works#
When a message arrives in Contact and matches a flow with tags, the flow system can trigger Strand workflows that include Surface scan steps. The tag-matching mechanism connects Contact messages to Strand workflows automatically:
- A message arrives at a Contact entity with tags (e.g.
["scan", "incoming"]) - Contact checks for Strand workflows whose trigger tags overlap with the message tags
- Any matching workflow is triggered with the message data as the workflow payload
- If the workflow includes a
surface.platformnode, the payload is scanned
The scan runs as part of the Strand workflow execution; it does not block or delay Contact message delivery.
Tag-matching mechanism#
Workflow triggering and connector routing match tags differently, and this is the most common integration mistake:
- Strand workflows fire only when the workflow's trigger tags contain
- Contact connectors use
$inmatching: any single tag overlap triggers
every tag on the message (workflow tags ⊇ message tags). Extra tags on the workflow are fine; a message tag the workflow doesn't have blocks the match.
the connector. Multiple connectors can fire for one message (deduplicated automatically).
For example, a message tagged ["environmental", "data_quality"] does not fire a workflow whose trigger tags are only ["data_quality"], because that workflow is missing environmental. A workflow tagged ["environmental", "data_quality"] does fire for a message tagged ["data_quality"]. Keep message tags minimal and give workflows every tag they should respond to.
Contact trigger payload#
When Contact triggers a Strand workflow, the trigger payload has this structure:
{
"payload": {
"source": "contact",
"contactFlow": {
"flowId": "flow_abc",
"flowName": "sensor_validation",
"executionId": "exec_xyz"
},
"entity": {
"id": "entities/temperature_sensor_01",
"name": "temperature_sensor_01"
},
"messageId": "6614abcd1234ef5678901234",
"tags": ["environmental", "data_quality"],
"data": {
"temperature": 85.2,
"humidity": 45,
"timestamp": "2025-03-24T10:00:00Z"
},
"timestamp": "2025-03-24T10:00:01Z",
"trigger_depth": 0
},
"meta": {
"account_id": "12345",
"workflow_id": "6208953a_f5b1_46ae_8bfc_837054cece0f",
"workflow_name": "sensor_validation",
"trigger_source": "contact_flow",
"triggered_at": "2025-03-24T10:00:01.412Z"
}
}
Accessing fields in Strand workflows#
In Strand workflow nodes, the Contact trigger data is available via the data field in the payload. Because the trigger payload wraps the message data inside a data key, the actual message content is accessed as:
{{ payload.data }}
For example, to scan the message content in a Surface node:
{{ payload.data | tojson }}
To access specific fields from the original message:
{{ payload.data.temperature }}
{{ payload.entity.name }}
{{ payload.tags }}
Available trigger fields#
| Field | Type | Description |
|---|---|---|
payload.data |
object | The actual message data payload |
payload.tags |
array | Tags that matched the trigger |
payload.messageId |
string | Contact message ID |
payload.timestamp |
string | ISO 8601 timestamp of the trigger |
payload.entity.id |
string | Entity resource path |
payload.entity.name |
string | Entity display name |
payload.contactFlow.flowId |
string | Contact flow ID that triggered |
payload.contactFlow.flowName |
string | Contact flow name |
payload.contactFlow.executionId |
string | Unique execution ID for this trigger |
meta.account_id |
string | Tendrl account ID |
Setup#
Surface scanning in Contact runs through a Strand workflow that contains a Surface scan node. To set it up:
- In Strand, build a workflow with a Surface scan node (see Strand integration) and give the workflow trigger tags (e.g.
["scan"]) - Turn on Expose to Contact in that workflow's settings. A correctly tagged workflow that is not exposed never fires
- If you restrict the workflow's allowed sources, make sure the sending entity is on the list. An empty list means any entity
- In Contact, tag the messages you want screened, using only tags the workflow also has (the workflow's tags must cover every tag on the message)
- Matching messages automatically trigger the workflow, which scans the payload
No additional API key configuration is required; the platform handles authentication internally. Your account determines the monthly scan limit.
Scan result variables#
When a Strand workflow triggered from Contact includes a Surface scan node, the scan result fields are available for downstream conditions:
| Variable | Type | Description |
|---|---|---|
payload.threat_level |
string | Clean, Informational, Suspicious, or Malicious, or Rejected / Error when nothing was scanned |
payload.score |
integer | 0-100 safety score (higher is safer) |
payload.recommended_action |
string | Allow, Review, or Block |
payload.safe |
boolean | true only when Surface scored the file and threat_level is not in reject_on. Never true for Rejected or Error |
payload.primary_threat |
string | Top matched threat label, or empty string |
payload.request_id |
string | Scan correlation ID |
payload.iocs |
array | Indicators of compromise |
payload.coverage |
string | full, partial or minimal — how far analysis reached for this format |
payload.coverage_note |
string | A sentence explaining that coverage level |
Example branch condition#
{{ payload.threat_level == "Malicious" }} -> block or alert
{{ payload.safe == true }} -> forward to downstream connector
Flow execution details#
In the Contact flow detail view, each step shows its result, including the Surface scan node's output: threat level, safety score (higher is safer), recommended action, and IOCs.
Limits#
Scans in Contact-triggered workflows count against your Surface monthly scan quota. When the quota is exhausted, payloads are forwarded without scanning; no messages are dropped.
Related#
- Strand integration: surface in Strand workflows
Tendrl