External Approval Links
Allow reviewers to approve or reject workflow steps directly from email, Slack, or any external channel, with no login required.
Overview#
When a workflow hits an approval node, Strand can generate a secure, signed URL that allows the recipient to take action without logging into the application. This is useful when approvers are not regular Strand users, or when you want to enable quick action from mobile devices or notification channels.
How It Works#
- Workflow execution pauses at an approval node
- Strand generates a cryptographically signed token
- A URL is created:
https://app.tendrl.com/strand/approve/{token} - The URL is delivered via the configured notification connector (Slack, email, etc.)
- The recipient clicks the link and sees a standalone approval page (the notification and the approval page also link to the run itself,
/strand/#runs/{run_id}/{node_id}, which needs a Tendrl sign-in) - They can approve or reject with an optional comment
- The workflow resumes (on approve) or stops (on reject)
Setup#
Option A: Built-In Notification#
Configure notify_connector_id directly on the approval node:
Slack example:
{
"message": "New expense report: ${{ payload.amount }} from {{ payload.employee }}",
"instructions": "Approve if amount is within department budget.",
"notify_connector_id": "slack-connector-id",
"notify_channel": "#expense-approvals",
"token_expiry_hours": 72
}
Email example:
{
"message": "New expense report: ${{ payload.amount }} from {{ payload.employee }}",
"instructions": "Approve if amount is within department budget.",
"notify_connector_id": "email-connector-id",
"notify_recipients": "{{ payload.manager_email }}",
"token_expiry_hours": 72
}
With a Tendrl Email connector, the recipients must be at your organization's email domain or be members of your account (details). Use an SMTP connector to notify anyone else. Each approval email counts as one send toward Tendrl Email's 1,000 per account per month. If a send is refused, the run still waits for approval and the step shows the notification as failed with the reason.
Strand will automatically:
- Generate a signed approval link
- Send the approval message plus link to the specified channel or recipients, with a View the run link that opens the run on its Approval Required panel (sign-in required)
- Format the notification appropriately for the connector type (Slack message, HTML email, or text message)
Option B: Connector Before Approval#
For full control over the notification format, place a connector node before the approval node in your workflow graph. You can build the approval URL yourself using the token available in the notification metadata, or simply send a message saying "check Strand for a pending approval."
Security#
External approval links are secured with multiple layers:
- Cryptographic signatures: Each token is signed so that any tampering invalidates it
- Automatic expiry: Tokens expire after the configured
token_expiry_hours(default: 7 days, max: 30 days) - Single-use enforcement: Once approved or rejected, the link cannot be reused
- Rate limiting: Public approval endpoints are rate-limited to prevent abuse
- Uniform error responses: All failure modes return the same generic error to prevent information leakage
URL Configuration#
Approval links point to your Strand application's public URL. This is configured at the platform level by your administrator. No user-side setup is needed.
API Reference#
Get Approval Details#
GET /api/approval-actions/{token}
Returns approval context for display on the approval page. It includes run_id and node_id so the page can link to the run, but never the step's input or output data: this endpoint needs only the token, so run data stays behind sign-in.
Response:
{
"status": "waiting_approval",
"approval_message": "New expense report: $500 from Jane",
"approval_instructions": "Approve if within budget.",
"workflow_name": "Expense Approval",
"run_id": "6f1c2d3e_0a1b_4c5d_9e8f_0123456789ab",
"node_id": "approval_1",
"run_status": "waiting_approval",
"already_actioned": false,
"actioned_at": null
}
Approve via Token#
POST /api/approval-actions/{token}/approve
Body:
{
"comment": "Approved; within budget."
}
Response:
{
"message": "Approved successfully. Workflow is resuming."
}
Reject via Token#
POST /api/approval-actions/{token}/reject
Body:
{
"comment": "Over budget; please revise."
}
Response:
{
"message": "Rejected successfully.",
"run_status": "completed_with_errors"
}
Connector Payload#
When using notify_connector_id, the connector receives a payload like:
{
"payload": {
"approval_message": "New expense report: $500 from Jane",
"approval_instructions": "Approve if within budget.",
"approval_url": "https://app.tendrl.com/strand/approve/eyJzIjoi...",
"workflow_name": "Expense Approval",
"run_id": "run_abc123",
"step_run_id": "step_xyz789"
},
"meta": {
"source": "approval_notification"
}
}
You can configure the connector's message template to format this however you like (e.g., a Slack message with action buttons linking to the approval URL).
Best Practices#
- Set
token_expiry_hoursappropriate to your use case (shorter for time-sensitive approvals) - Include enough context in the
messageso the reviewer can decide without opening the app - Use
instructionsto document the review criteria - Test the approval flow end-to-end in development before deploying
Related#
- Approval Node - Approval node reference
- Connectors - Setting up notification connectors
- Jinja2 Templating - Dynamic content in approval messages
Tendrl