LoopFour

Your First Real Workflow

Build a workflow that responds to webhooks and sends notifications

Your First Real Workflow

In this tutorial, you'll build a real-world workflow that:

  1. Triggers when a Stripe payment is received
  2. Transforms the payment data
  3. Sends a Slack notification

Prerequisites

  • Completed the Quickstart
  • A Slack workspace (for notifications)
  • Optional: Stripe account (for live testing)

What We're Building

graph LR
    A[Stripe Webhook] --> B[Transform Data]
    B --> C[Send Slack Message]

When a payment is received in Stripe, the workflow will send a message to your Slack channel with payment details.

Step 1: Create the Workflow

Create a workflow with a webhook trigger:

curl -X POST http://localhost:3000/api/v1/workflows \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Payment Notification",
    "description": "Send Slack notification when payment received",
    "trigger": {
      "type": "webhook",
      "provider": "stripe",
      "events": ["payment_intent.succeeded", "charge.succeeded"]
    },
    "steps": [
      {
        "id": "transform-payment",
        "type": "transform",
        "name": "Format Payment Data",
        "config": {
          "mapping": {
            "amount": "{{input.data.object.amount}}",
            "currency": "{{input.data.object.currency}}",
            "customer_email": "{{input.data.object.receipt_email}}",
            "payment_id": "{{input.data.object.id}}",
            "formatted_amount": "${{input.data.object.amount / 100}}"
          }
        }
      },
      {
        "id": "notify-slack",
        "type": "slack",
        "name": "Send Slack Notification",
        "config": {
          "action": "sendMessage",
          "channel": "#payments",
          "text": "Payment received! {{steps.transform-payment.output.formatted_amount}} from {{steps.transform-payment.output.customer_email}}"
        }
      }
    ]
  }'

Save the workflow ID from the response.

Step 2: Connect Slack

Before activating, connect your Slack workspace:

# Get the OAuth URL for Slack
curl "http://localhost:3000/api/v1/connections/slack/auth-url" \
  -H "x-api-key: YOUR_API_KEY"

This returns a URL to complete the Slack OAuth flow:

{
  "success": true,
  "data": {
    "url": "https://slack.com/oauth/v2/authorize?client_id=..."
  }
}

Visit the URL, authorize your workspace, and you'll be redirected back.

Step 3: Activate the Workflow

curl -X POST http://localhost:3000/api/v1/workflows/{WORKFLOW_ID}/activate \
  -H "x-api-key: YOUR_API_KEY"

Step 4: Configure Stripe Webhook

Add your webhook URL to Stripe's dashboard:

https://your-domain.com/webhooks/stripe/{COMPANY_ID}

Or for local development with a tunnel like ngrok:

https://your-ngrok-id.ngrok.io/webhooks/stripe/{COMPANY_ID}

Select the events:

  • payment_intent.succeeded
  • charge.succeeded

Step 5: Test the Workflow

You can test without Stripe by manually triggering the workflow:

curl -X POST http://localhost:3000/api/v1/workflows/{WORKFLOW_ID}/run \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "type": "payment_intent.succeeded",
      "data": {
        "object": {
          "id": "pi_test123",
          "amount": 9900,
          "currency": "usd",
          "receipt_email": "customer@example.com"
        }
      }
    }
  }'

You should see a message in your #payments Slack channel!

Understanding the Workflow

Trigger Configuration

{
  "type": "webhook",
  "provider": "stripe",
  "events": ["payment_intent.succeeded", "charge.succeeded"]
}
  • type: "webhook" - Triggered by incoming webhooks
  • provider: "stripe" - Expects Stripe webhook format and signature verification
  • events - Only trigger on these specific event types

Template Variables

The workflow uses template variables to access data:

VariableDescription
{{input.*}}Access webhook payload data
{{steps.{id}.output.*}}Access output from previous steps
{{now}}Current timestamp

Transform Step

{
  "type": "transform",
  "config": {
    "mapping": {
      "formatted_amount": "${{input.data.object.amount / 100}}"
    }
  }
}

The transform step maps input data to a new structure. You can:

  • Rename fields
  • Compute values (like dividing cents by 100)
  • Combine fields

Slack Step

{
  "type": "slack",
  "config": {
    "action": "sendMessage",
    "channel": "#payments",
    "text": "..."
  }
}

The Slack building block supports:

  • sendMessage - Send to channel or DM
  • sendEphemeral - Visible to one user only
  • addReaction - Add emoji reaction
  • And more (see Slack Block docs)

Adding Conditional Logic

Let's enhance the workflow to only notify for payments over $100:

{
  "steps": [
    {
      "id": "transform-payment",
      "type": "transform",
      "config": {
        "mapping": {
          "amount_dollars": "{{input.data.object.amount / 100}}",
          "customer_email": "{{input.data.object.receipt_email}}"
        }
      }
    },
    {
      "id": "check-amount",
      "type": "condition",
      "name": "Check if High Value",
      "config": {
        "conditions": {
          "left": "{{steps.transform-payment.output.amount_dollars}}",
          "operator": "gte",
          "right": "100"
        },
        "then": ["notify-slack"],
        "else": []
      }
    },
    {
      "id": "notify-slack",
      "type": "slack",
      "config": {
        "action": "sendMessage",
        "channel": "#high-value-payments",
        "text": "High value payment: ${{steps.transform-payment.output.amount_dollars}} from {{steps.transform-payment.output.customer_email}}"
      }
    }
  ]
}

Viewing Run History

Check workflow run history:

curl "http://localhost:3000/api/v1/workflows/{WORKFLOW_ID}/runs" \
  -H "x-api-key: YOUR_API_KEY"

Get details for a specific run:

curl "http://localhost:3000/api/v1/runs/{RUN_ID}" \
  -H "x-api-key: YOUR_API_KEY"

The response shows each step's status, timing, and output:

{
  "success": true,
  "data": {
    "id": "run_xyz...",
    "status": "completed",
    "steps": [
      {
        "id": "transform-payment",
        "status": "completed",
        "output": {
          "amount_dollars": 99,
          "customer_email": "customer@example.com"
        }
      },
      {
        "id": "notify-slack",
        "status": "completed",
        "output": {
          "ts": "1234567890.123456",
          "channel": "C123ABC"
        }
      }
    ]
  }
}

Next Steps