← Documentation home

Event bridge: from your form or SaaS to a DripPulse workflow

DripPulse does not assign a generated “inbound workflow URL” per tenant for arbitrary JSON today. The supported, API-first pattern is: your infrastructure receives the event, then calls DripPulse to start or continue work.

Canonical flow

  1. Inbound to you: A form, product backend, Stripe webhook, or partner POSTs to your HTTPS endpoint (you own auth, validation, and rate limits).
  2. Normalize: Map the payload into contact fields your workflow expects (email, first_name, lead_ref, …).
  3. Start the sequence: Call POST /api/v1/workflows/:workflow_id/enrollments/bulk with contacts: [...] — one object per person entering the nurture — using an organization API key.
  4. Alternative: For one-off runs with a full JSON payload, deploy a workflow agent and call POST /api/v1/agents/:id/execute with type: "workflow" (see agents).

End-to-end example (enrollments/bulk)

Your server receives a signup, creates or links a CRM lead, then enrolls:

// Pseudocode: your backend after validating the inbound POST
const lead = await crm.createLead({ email, first_name, company });
await fetch(`https://drippulse.io/api/v1/workflows/${WORKFLOW_ID}/enrollments/bulk`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${DRIPPULSE_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': signupEventId   // your header; see below
  },
  body: JSON.stringify({
    contacts: [{
      email,
      first_name,
      lead_ref: lead.public_id,
      company_name: company
    }]
  })
});

There is no DripPulse-global idempotency header for bulk enroll yet; treat duplicate prevention as your responsibility (see next section).

Copy-paste: HTML form → your server → enrollments/bulk with dedupe

Example: a public landing page posts to your API; you store an idempotency key (here, a simple in-memory Set — use Redis/DB in production) before calling DripPulse.

// Express (Node 18+) — install: express
import express from 'express';

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

const DRIPPULSE_API_KEY = process.env.DRIPPULSE_API_KEY;
const WORKFLOW_ID = process.env.DRIPPULSE_WORKFLOW_ID;
const seenSignupIds = new Set(); // replace with durable store + TTL

app.post('/api/signup', async (req, res) => {
  const email = req.body.email;
  const first_name = req.body.first_name || '';
  const signupId = req.body.signup_id || `${email}:${req.body.ts || ''}`;

  if (seenSignupIds.has(signupId)) {
    return res.status(200).json({ ok: true, deduped: true });
  }
  seenSignupIds.add(signupId);

  const r = await fetch(`https://drippulse.io/api/v1/workflows/${WORKFLOW_ID}/enrollments/bulk`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${DRIPPULSE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      contacts: [{ email, first_name, lead_ref: signupId }],
      timezone: 'UTC',
    }),
  });
  const body = await r.json().catch(() => ({}));
  if (!r.ok) {
    seenSignupIds.delete(signupId); // allow retry on failure
    return res.status(502).json({ error: 'drippulse_enroll_failed', status: r.status, body });
  }
  return res.status(200).json({ ok: true, enrollment_ids: body.enrollment_ids });
});

app.listen(3000);

DripPulse does not honor the browser’s Idempotency-Key header on this route; dedupe keys must live in your application state (or CRM) before calling enrollments/bulk.

Idempotency guidance

SurfaceBehavior
POST .../enrollments/bulkEach successful call creates new enrollment rows. Before calling, dedupe on your side (e.g. unique constraint on signup_event_id, or check GET .../enrollments / CRM state). You may DELETE .../enrollments/:id to remove mistaken test rows.
POST .../agents/:id/execute (workflow agent)Supports idempotency_key in the documented execute body — safe replays return the stored outcome.

How this relates to the “Webhook” step

The builder Webhook input step documents what you expect an external caller to do (reference URL, method). It does not open a listener during POST .../test or step ticks. Starting real runs from the internet always goes through your bridge, then enrollments/bulk or agents, as above. For HTTP out to a vendor during a run, use an Outbound Webhook step.

Release note (enrollments/bulk)

If you saw HTTP 201 with created_count: 0 before 2026-04-10, that was a bug for common JSON clients. Current behavior is summarized in the repo root CHANGELOG.md and in the API reference — workflows.

See also: External agents guide · Nurture recipe · Programmatic quickstart