LoopFour

Action Steps

Execute integration actions in workflows

Action Steps

Action steps execute operations on external services through connectors. They are the primary way to interact with integrated systems like Stripe, Salesforce, HubSpot, QuickBooks, and more.

Configuration

{
  "id": "create-invoice",
  "type": "action",
  "name": "Create Stripe Invoice",
  "action": "stripe.createInvoice",
  "config": {
    "customerId": "{{input.customer_id}}",
    "items": [
      {
        "price": "price_xxx",
        "quantity": 1
      }
    ]
  }
}
FieldTypeRequiredDescription
idstringYesUnique step identifier
typestringYesMust be "action"
namestringYesHuman-readable name
actionstringYesAction identifier (e.g., stripe.createInvoice)
configobjectYesAction-specific configuration
retryConfigobjectNoRetry configuration
errorHandlingobjectNoError handling strategy
timeoutMsnumberNoTimeout in milliseconds

Action Format

Actions follow the pattern {connector}.{actionName}:

stripe.createInvoice
salesforce.updateOpportunity
hubspot.createContact
quickbooks.listCustomers
slack.sendMessage

Available Connectors

ConnectorDescriptionExample Actions
stripePayment processingcreateInvoice, getCustomer, createSubscription
salesforceCRM operationsgetAccount, updateOpportunity, query
hubspotMarketing/CRMcreateContact, getDeal, searchContacts
quickbooksAccountingcreateInvoice, getCustomer, createPayment
pandadocDocument signingcreateDocument, sendDocument, getStatus
netsuiteERP operationscreateCustomer, createInvoice, suiteql
slackMessagingsendMessage, openModal, addReaction
emailEmail sendingsend, send_template, schedule

See Integrations for complete action documentation per connector.

Template Variables

Use template syntax to reference input and previous step outputs:

{
  "id": "update-contact",
  "type": "action",
  "action": "hubspot.updateContact",
  "config": {
    "contactId": "{{steps.lookup.output.id}}",
    "properties": {
      "company": "{{input.company_name}}",
      "lifecyclestage": "customer",
      "last_order_date": "{{now}}"
    }
  }
}

Available Template Contexts

ContextExampleDescription
input{{input.customer_id}}Workflow trigger input
steps{{steps.step-id.output.field}}Previous step output
variables{{variables.taxRate}}Workflow variables
env{{env.API_URL}}Environment variables
now{{now}}Current ISO timestamp

Nested Access

{
  "config": {
    "email": "{{input.customer.contact.email}}",
    "amount": "{{steps.calculate.output.totals.grandTotal}}"
  }
}

Error Handling

Retry Configuration

Configure automatic retries for transient failures:

{
  "id": "api-call",
  "type": "action",
  "action": "stripe.createInvoice",
  "config": { ... },
  "retryConfig": {
    "maxAttempts": 3,
    "initialDelayMs": 1000,
    "maxDelayMs": 30000,
    "backoffMultiplier": 2
  }
}
FieldTypeDescription
maxAttemptsnumberMaximum retry attempts (1-10)
initialDelayMsnumberInitial delay in ms (min: 100)
maxDelayMsnumberMaximum delay in ms (min: 1000)
backoffMultipliernumberExponential backoff multiplier (1-10)

Error Handling Strategy

{
  "id": "optional-sync",
  "type": "action",
  "action": "hubspot.updateContact",
  "config": { ... },
  "errorHandling": {
    "strategy": "continue",
    "fallback": {
      "synced": false,
      "error": "Sync failed"
    }
  }
}
StrategyBehavior
failStop workflow, mark run as failed (default)
continueLog error, continue with fallback output
retryRetry with retryConfig settings

Timeout

{
  "id": "slow-api",
  "type": "action",
  "action": "netsuite.suiteql",
  "config": { ... },
  "timeoutMs": 60000
}

Output

Action outputs are stored and available to subsequent steps:

// Access in later steps
{{steps.create-invoice.output.id}}
{{steps.create-invoice.output.number}}
{{steps.create-invoice.output.status}}
{{steps.create-invoice.output.amount_due}}

Checking Output Existence

{
  "id": "check-result",
  "type": "condition",
  "config": {
    "conditions": {
      "left": "{{steps.create-invoice.output.id}}",
      "operator": "exists"
    },
    "then": ["send-notification"],
    "else": ["handle-error"]
  }
}

Common Patterns

Create Then Update

{
  "steps": [
    {
      "id": "create-customer",
      "type": "action",
      "action": "stripe.createCustomer",
      "config": {
        "email": "{{input.email}}",
        "name": "{{input.name}}"
      }
    },
    {
      "id": "create-invoice",
      "type": "action",
      "action": "stripe.createInvoice",
      "config": {
        "customer": "{{steps.create-customer.output.id}}",
        "auto_advance": true
      }
    }
  ]
}

Conditional Action

{
  "steps": [
    {
      "id": "check-exists",
      "type": "action",
      "action": "hubspot.searchContacts",
      "config": {
        "filterGroups": [{
          "filters": [{
            "propertyName": "email",
            "operator": "EQ",
            "value": "{{input.email}}"
          }]
        }]
      }
    },
    {
      "id": "route-action",
      "type": "condition",
      "config": {
        "conditions": {
          "left": "{{steps.check-exists.output.total}}",
          "operator": "gt",
          "right": "0"
        },
        "then": ["update-contact"],
        "else": ["create-contact"]
      }
    }
  ]
}

With Fallback

{
  "id": "sync-to-crm",
  "type": "action",
  "action": "salesforce.updateAccount",
  "config": {
    "id": "{{input.sf_account_id}}",
    "fields": { "Website": "{{input.website}}" }
  },
  "errorHandling": {
    "strategy": "continue",
    "fallback": {
      "synced": false,
      "syncError": "Failed to update Salesforce"
    }
  }
}

Connector-Specific Examples

Stripe

{
  "id": "create-payment",
  "type": "action",
  "action": "stripe.createPaymentIntent",
  "config": {
    "amount": "{{input.amount}}",
    "currency": "usd",
    "customer": "{{input.stripe_customer_id}}",
    "metadata": {
      "order_id": "{{input.order_id}}"
    }
  }
}

Salesforce

{
  "id": "query-accounts",
  "type": "action",
  "action": "salesforce.query",
  "config": {
    "soql": "SELECT Id, Name, Website FROM Account WHERE Type = 'Customer' LIMIT 100"
  }
}

Slack

{
  "id": "notify-team",
  "type": "action",
  "action": "slack.sendMessage",
  "config": {
    "channel": "#sales-notifications",
    "text": "New deal closed: {{input.deal_name}} for ${{input.amount}}"
  }
}

Troubleshooting

Action Not Found

{
  "error": {
    "code": "ACTION_NOT_FOUND",
    "message": "Action 'stripe.unknownAction' not found"
  }
}

Verify the action name matches the connector's available actions.

Connection Required

{
  "error": {
    "code": "CONNECTION_REQUIRED",
    "message": "No active connection found for provider 'salesforce'"
  }
}

Ensure you have an active OAuth connection for the connector.

Rate Limited

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded for provider"
  }
}

Add retry configuration with exponential backoff.

Next Steps