Lindy CI Integration
Overview
Lindy agents run on Lindy's managed platform — CI/CD tests your integration code: webhook receivers, callback handlers, and application logic that interacts with Lindy agents. Test webhook signature verification, payload processing, and error handling without hitting live Lindy endpoints.
Prerequisites
- GitHub repository with Actions enabled
- Lindy API key and webhook secret stored as GitHub secrets
- Node.js project with webhook receiver code
- Completed
lindy-install-authsetup
Instructions
Step 1: Store Secrets in GitHub
gh secret set LINDY_API_KEY --body "lnd_live_xxxxxxxxxxxx"
gh secret set LINDY_WEBHOOK_SECRET --body "whsec_xxxxxxxxxxxx"
Step 2: Create GitHub Actions Workflow
# .github/workflows/lindy-integration.yml
name: Lindy Integration Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run unit tests
run: npm test
env:
LINDY_WEBHOOK_SECRET: ${{ secrets.LINDY_WEBHOOK_SECRET }}
- name: Validate webhook handler
run: npm run test:webhook
- name: Connectivity check (non-blocking)
continue-on-error: true
run: |
curl -s -o /dev/null -w "%{http_code}" \
-X POST "https://public.lindy.ai/api/v1/webhooks/health" \
-H "Authorization: Bearer ${{ secrets.LINDY_API_KEY }}"
Step 3: Write Webhook Handler Tests
// __tests__/webhook-handler.test.ts
import { describe, it, expect, vi } from 'vitest';
import request from 'supertest';
import { app } from '../src/server';
describe('Lindy Webhook Handler', () => {
const VALID_SECRET = process.env.LINDY_WEBHOOK_SECRET || 'test-secret';
it('rejects requests without auth header', async () => {
const res = await request(app)
.post('/lindy/callback')
.send({ event: 'test' });
expect(res.status).toBe(401);
});
it('rejects requests with wrong auth token', async () => {
const res = await request(app)
.post('/lindy/callback')
.set('Authorization', 'Bearer wrong-token')
.send({ event: 'test' });
expect(res.status).toBe(401);
});
it('accepts requests with valid auth token', async () => {
const res = await request(app)
.post('/lindy/callback')
.set('Authorization', `Bearer ${VALID_SECRET}`)
.set('Content-Type', 'application/json')
.send({
taskId: 'task_123',
status: 'completed',
result: { summary: 'Test result' },
});
expect(res.status).toBe(200);
expect(res.body.received).toBe(true);
});
it('handles malformed payload gracefully', async () => {
const res = await request(app)
.post('/lindy/callback')
.set('Authorization', `Bearer ${VALID_SECRET}`)
.set('Content-Type', 'application/json')
.send('not-json');
expect(res.status).toBeLessThan(500);
});
it('processes webhook payload fields correctly', async () => {
const payload = {
taskId: 'task_456',
status: 'completed',
result: {
classification: 'billing',
sentiment: 'neutral',
summary: 'Customer asks about invoice #789',
},
};
const res = await request(app)
.post('/lindy/callback')
.set('Authorization', `Bearer ${VALID_SECRET}`)
.set('Content-Type', 'application/json')
.send(payload);
expect(res.status).toBe(200);
});
});
Step 4: Add Smoke Test for Live Connectivity
// __tests__/connectivity.test.ts
import { describe, it, expect } from 'vitest';
describe('Lindy Connectivity (smoke)', () => {
it('can reach Lindy webhook endpoint', async () => {
const webhookUrl = process.env.LINDY_WEBHOOK_URL;
if (!webhookUrl) {
console.warn('LINDY_WEBHOOK_URL not set, skipping connectivity test');
return;
}
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LINDY_WEBHOOK_SECRET}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ test: true, ci: true }),
});
expect(response.ok).toBe(true);
});
});
Step 5: PR Status Check
# Add to existing workflow
- name: Post test results
if: always()
uses: actions/github-script@v7
with:
script: |
const status = '${{ job.status }}' === 'success' ? 'Passed' : 'Failed';
if (context.payload.pull_request) {
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: `Lindy Integration Tests: **${status}**`
});
}
Test Categories
| Category | What It Tests | Requires Live Lindy? | |----------|-------------|---------------------| | Auth verification | Webhook signature checking | No | | Payload processing | Data extraction and transformation | No | | Error handling | Malformed input, edge cases | No | | Connectivity | Webhook endpoint reachability | Yes (non-blocking) | | End-to-end | Full trigger -> callback cycle | Yes |
Error Handling
| Issue | Cause | Solution |
|-------|-------|----------|
| Secret not found in CI | Not configured | gh secret set LINDY_WEBHOOK_SECRET |
| Connectivity test fails | Lindy endpoint unreachable in CI | Mark as continue-on-error: true |
| Tests timeout | Network latency | Set timeout-minutes: 10 on job |
| Flaky live tests | Lindy processing delay | Add retry logic or mock external calls |
Resources
Next Steps
Proceed to lindy-deploy-integration for deployment automation.