Custom Triggers & Event-Driven Beats

Wiring GitHub push events, Slack messages, or custom webhooks as non-cron triggers to wake an agent on demand. The trigger registration API and idempotency handling.

Custom Heartbeat Triggers

What you'll learn

Supplement the cron schedule with event-based triggers — fire the heartbeat when a webhook arrives, a database row changes, or a human presses a button.

Why Custom Triggers

The cron heartbeat runs every N minutes regardless of whether work exists. Custom triggers let you react in near-real-time:

Trigger Types

Paperclip supports three trigger types beyond the cron:

Type How it fires
Webhook trigger External system POSTs to a Paperclip endpoint
Database trigger PostgreSQL NOTIFY → heartbeat listen
Manual trigger API call or UI button

Webhook Trigger

Register a webhook URL per company. External systems POST to it:

# Your GitHub Actions workflow posts to this URL on PR opened:
curl -X POST https://your-paperclip.com/webhooks/your-company/github \
  -H "X-Hub-Signature-256: sha256=..." \
  -d '{"action": "opened", "pull_request": {"number": 42, ...}}'

Paperclip verifies the signature, extracts relevant data, creates an issue, and immediately fires a heartbeat tick for the company.

Configuration in Company Settings → Webhooks → New Webhook:

{
  "source": "github",
  "events": ["pull_request.opened"],
  "auto_create_issue": true,
  "issue_template": {
    "title": "Review PR #{{payload.pull_request.number}}",
    "assigned_agent": "code-reviewer",
    "priority": "high"
  }
}

Database NOTIFY Trigger

For internal Paperclip-to-Paperclip automation, use PostgreSQL's NOTIFY system:

-- When a new row is inserted into an external table, notify Paperclip
CREATE OR REPLACE FUNCTION notify_paperclip_on_ticket()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  PERFORM pg_notify(
    'paperclip_trigger',
    json_build_object(
      'company_id', 'your-company-id',
      'event', 'ticket_created',
      'ticket_id', NEW.id,
      'subject', NEW.subject
    )::text
  );
  RETURN NEW;
END;
$$;

CREATE TRIGGER on_ticket_insert
AFTER INSERT ON support_tickets
FOR EACH ROW EXECUTE FUNCTION notify_paperclip_on_ticket();

Paperclip listens on the paperclip_trigger channel:

// packages/server/src/lib/heartbeat/listener.ts
db.listen('paperclip_trigger', async (payload) => {
  const data = JSON.parse(payload);
  await createIssueAndFire(data);
});

Manual Trigger API

You already know this one — useful for CI/CD pipelines or developer tooling:

# Fire heartbeat for a specific company
curl -X POST http://localhost:3100/api/heartbeat/run \
  -H "Content-Type: application/json" \
  -d '{"company_id": "'${COMPANY_ID}'"}'

Add it to a GitHub Actions step to process CI results immediately:

- name: Notify Paperclip
  run: |
    curl -X POST ${{ secrets.PAPERCLIP_URL }}/api/heartbeat/run \
      -H "Content-Type: application/json" \
      -d '{"company_id": "${{ secrets.COMPANY_ID }}"}'

Checkpoint ✓

Sign in for progress tracking & Q&A →