Integrations

Custom triggers: start a campaign from any webhook

Turn any form, CRM, Stripe or Calendly event into LinkedIn outreach: add the lead and enroll them in a Prosyo campaign with n8n, Zapier, Make or code.

On this page

A custom trigger starts Prosyo outreach when something happens in another app. For example:

  • someone fills in your website form → they're added to a list and enrolled in a LinkedIn sequence,
  • a trial signup in Stripe → the founder sends a personal LinkedIn touch,
  • a Calendly no-show → a gentle follow-up sequence starts,
  • a HubSpot deal moves to closed-lost → a revival campaign starts in 90 days.

Webhooks already let Prosyo send events out. See Signed webhooks. Custom triggers work the other way: any app → Prosyo.


How it works#

Every custom trigger follows the same three steps:

Text
Your app ──webhook──▶ Trigger URL (n8n / Zapier / Make / your code)
                          │
                          ├─ 1. POST /api/v1/ext/leads      → add or update the person on a list
                          ├─ 2. POST /api/v1/ext/campaigns  → enroll them in a campaign
                          └─ 3. (optional) POST /api/v1/ext/enrich or /bulk
StepEndpointResult
1. Add the leadPOST /api/v1/ext/leadsReturns prospectIds for new and existing people
2. EnrollPOST /api/v1/ext/campaignsAdds them to a campaign. A running campaign starts sending inside working hours.
3. OptionalPOST /api/v1/ext/enrichResearch the person or write a first-touch line (1 credit each)
3. OptionalPOST /api/v1/ext/bulkTag, change stage, or add to another list

Everything still follows your campaign's daily limits, working hours, delay between actions and stop-on-reply. A trigger can't make Prosyo send faster than your settings allow.

Before you start#

  1. Create the campaign you want people to enter and launch it. If it's still a draft, people are enrolled but nothing sends until you launch.
  2. Get the campaign ID. Call GET /api/v1/ext/campaigns (below), or open the campaign in Prosyo and copy the ID from the address bar: app.prosyo.com/campaigns/<campaign-id>.
  3. Generate an API token in Integrations → Chrome extension & API → Generate API token. Copy it now, because it's shown once.
Important

Treat the token like a password. Store it in your automation tool's credentials, not in plain text, and never put it in website code that visitors can see.


Workflow: Webhook → HTTP Request (add lead) → HTTP Request (enroll)

  1. Webhook node
    • HTTP Method: POST
    • Path: prosyo-trigger
    • Respond: Immediately
    • Copy the Production URL. This is your custom trigger URL. Paste it into your form tool, CRM or app as the webhook destination.
  2. HTTP Request: add lead
    • Method POST, URL https://app.prosyo.com/api/v1/ext/leads
    • Authentication: Header Auth → Authorization: Bearer YOUR_TOKEN
    • Body (JSON):
      JSON
      {
        "listName": "Website form",
        "leads": [{
          "fullName": "{{ $json.body.name }}",
          "email": "{{ $json.body.email }}",
          "companyName": "{{ $json.body.company }}",
          "linkedinUrl": "{{ $json.body.linkedin }}"
        }]
      }
  3. HTTP Request: enroll
    • Method POST, URL https://app.prosyo.com/api/v1/ext/campaigns
    • Same Header Auth
    • Body (JSON):
      JSON
      {
        "campaignId": "YOUR_CAMPAIGN_ID",
        "prospectIds": {{ JSON.stringify($json.data.prospectIds) }}
      }
  4. Activate the workflow.

Add an If node between steps 2 and 3 to enroll only good-fit leads, for example when the company size is over 20 or the email domain isn't Gmail.

Option 2: Zapier#

  1. Trigger: your app (Typeform, Webflow, HubSpot, Stripe, Calendly, or anything else), or Webhooks by Zapier → Catch Hook for a generic trigger URL.
  2. Action: Webhooks by Zapier → Custom Request (add lead)
    • Method POST, URL https://app.prosyo.com/api/v1/ext/leads
    • Headers: Authorization: Bearer YOUR_TOKEN, Content-Type: application/json
    • Data: the lead JSON above, with fields inserted from the trigger
  3. Action: Webhooks by Zapier → Custom Request (enroll)
    • Method POST, URL https://app.prosyo.com/api/v1/ext/campaigns
    • Same headers
    • Data:
      JSON
      { "campaignId": "YOUR_CAMPAIGN_ID", "prospectIds": ["{{data__prospectIds}}"] }
      Insert Data Prospect Ids from step 2. If Zapier returns it as a comma-separated list, add a Formatter → Utilities → Line-item to text step, or use a Code by Zapier step to build the JSON array.

Option 3: Make#

  1. Webhooks → Custom webhook as the trigger. Copy the URL. That's your trigger URL.
  2. HTTP → Make a request to /api/v1/ext/leads (Raw JSON body, Authorization: Bearer header).
  3. HTTP → Make a request to /api/v1/ext/campaigns with campaignId and prospectIds mapped from step 2. Turn on Parse response in step 2 so you can map data.prospectIds.

Option 4: your own code (a trigger URL you host)#

Deploy this small function to Vercel, Netlify, Cloudflare Workers or any Node server. Its URL becomes your custom trigger. Set PROSYO_TOKEN, PROSYO_CAMPAIGN_ID and TRIGGER_SECRET as environment variables.

JavaScript
// POST /api/prosyo-trigger?key=TRIGGER_SECRET
export default async function handler(req, res) {
  if (req.method !== "POST" || req.query.key !== process.env.TRIGGER_SECRET) {
    return res.status(401).json({ ok: false });
  }
  const { name, email, company, linkedin, list = "Custom trigger" } = req.body || {};
  const headers = {
    Authorization: `Bearer ${process.env.PROSYO_TOKEN}`,
    "Content-Type": "application/json",
  };
  const lead = { fullName: name, companyName: company, linkedinUrl: linkedin };
  if (email) lead.email = email; // never send an empty email string

  const added = await fetch("https://app.prosyo.com/api/v1/ext/leads", {
    method: "POST",
    headers,
    body: JSON.stringify({ listName: list, leads: [lead] }),
  }).then((r) => r.json());
  if (!added.ok) return res.status(502).json(added);

  const enrolled = await fetch("https://app.prosyo.com/api/v1/ext/campaigns", {
    method: "POST",
    headers,
    body: JSON.stringify({
      campaignId: process.env.PROSYO_CAMPAIGN_ID,
      prospectIds: added.data.prospectIds,
    }),
  }).then((r) => r.json());

  return res.status(200).json({ ok: true, added: added.data, enrolled: enrolled.data });
}

cURL test:

Bash
curl -X POST "https://your-app.vercel.app/api/prosyo-trigger?key=YOUR_TRIGGER_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"name":"Alex Rivera","email":"alex@acme.com","company":"Acme","linkedin":"https://www.linkedin.com/in/alexrivera"}'

Python (Flask):

Python
import os, requests
from flask import Flask, request, jsonify

app = Flask(__name__)
API = "https://app.prosyo.com/api/v1/ext"
H = {"Authorization": f"Bearer {os.environ['PROSYO_TOKEN']}"}

@app.post("/prosyo-trigger")
def trigger():
    if request.args.get("key") != os.environ["TRIGGER_SECRET"]:
        return jsonify(ok=False), 401
    b = request.get_json(force=True)
    lead = {k: v for k, v in {
        "fullName": b.get("name"), "email": b.get("email"),
        "companyName": b.get("company"), "linkedinUrl": b.get("linkedin"),
    }.items() if v}
    added = requests.post(f"{API}/leads", headers=H,
                          json={"listName": "Custom trigger", "leads": [lead]}).json()
    ids = added["data"]["prospectIds"]
    enrolled = requests.post(f"{API}/campaigns", headers=H,
                             json={"campaignId": os.environ["PROSYO_CAMPAIGN_ID"], "prospectIds": ids}).json()
    return jsonify(ok=True, added=added["data"], enrolled=enrolled["data"])

Ready-made trigger recipes#

Trigger (your app)ListCampaign to enroll inExtra step
Website demo form (Webflow, WordPress, HubSpot Forms, Typeform)Inbound – demo form"Inbound: fast LinkedIn touch": visit → connect → message within 1 dayEnrich (personalized_line)
Gated content download (ebook, report)Content – [asset name]Soft 3-touch: connect → "hope the guide helps" → offer a callTag content
Webinar registration (Zoom, Livestorm, Luma)Event – [name]Webinar playbookTag registered
Free trial signup (Stripe, Paddle, your app)Trials – this monthFounder welcome on LinkedInStage interested
Calendly / Cal.com no-showNo-shows2-touch reschedule sequenceStage meetinginterested
HubSpot deal closed-lostClosed-lostRevival playbook (delay 60–90 days)Tag closed-lost
Job change (Clay, Common Room, UserGems)Job changersJob-change playbookEnrich
New hire posted (hiring-signal tool)Hiring signalsHiring-signal playbook
Slack slash command (/prosyo add <linkedin url>)Team referralsYour main ABM campaign
Google Sheets new rowSheet – [name]Any campaign

Chain triggers with Prosyo events#

Combine custom triggers with outgoing webhooks to build loops:

  • Reply received → an n8n Switch → if the reply is positive, POST /bulk with action: "stage" and stage: "interested", then create a CRM deal.
  • Stage changed to not_interestedPOST /bulk with action: "add_to_list" to a Nurture – 6 months list → a scheduled n8n job enrolls that list in a nurture campaign later.
  • Prospect created from a form → wait 10 minutes → POST /enrich → enroll once enrichment is done.

Rules and limits#

RuleValue
Leads per /leads requestUp to 100
Prospects per /campaigns enroll requestUp to 100
Rate limit, /leads60 requests / minute / user
Rate limit, /campaigns enroll30 requests / minute / user
Each lead needsA name, a LinkedIn URL or an email
Empty valuesLeave the field out or send null. Don't send "" for email.
Import quotaNew people count toward monthly imports. Existing people are updated and don't count.
DuplicatesMatched by LinkedIn URL and email. The same person is never enrolled twice in one campaign.
RoleThe token's user needs Member or higher to add leads and enroll

Troubleshooting#

ResponseMeaningFix
401Token missing, wrong, rotated or revokedGenerate a new token
404 "That list is not in this workspace"Wrong listIdUse listName, or get IDs from GET /boards
422 "No usable leads"Every lead was emptySend at least a name, email or LinkedIn URL
422 validation error on emailEmpty or invalid emailLeave email out when you don't have one
429Rate limitBatch leads (up to 100) or add a short delay
Enrolled but nothing sendsCampaign is a draft or paused, or it's outside working hoursLaunch or resume the campaign, and check working hours
skipped > 0 when enrollingAlready enrolled, Do not contact, or missing LinkedIn for a LinkedIn-only sequenceExpected. Prosyo protects people from double outreach.

See the full API reference.