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.
email, first_name, lead_ref, …).POST /api/v1/workflows/:workflow_id/enrollments/bulk with contacts: [...] — one object per person entering the nurture — using an organization API key.POST /api/v1/agents/:id/execute with type: "workflow" (see agents).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).
enrollments/bulk with dedupeExample: 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.
| Surface | Behavior |
|---|---|
POST .../enrollments/bulk | Each 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. |
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.
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