New customersGet 20% off your first payment on any plan.Not available for INR (₹) billing.Claim 20% off
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 (5 credits each) or write a first-touch line (2 credits 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. Only owners and admins can generate one. If your workspace already has a token and you saved it, use it. Otherwise click Rotate token: it replaces every token, so paste the new one into your Chrome extension and every other tool that used the old one.
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":"[email protected]","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 meeting → interested
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_interested → POST /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.
Tip

Not ready to start outreach yet? Send the event to Intent instead with POST /signals (for example a signup or form_submit). Intent adds it to the person's score, and your Intent playbooks can take it from there. See the API reference.

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. People matched by their LinkedIn profile are updated and don't count. A lead with no LinkedIn URL is always added as a new person, even if its email is already in Prosyo, and is then saved without that email.
DuplicatesLeads are matched to people already in Prosyo by their LinkedIn profile, so send linkedinUrl whenever you have it. The same person is never enrolled twice in one campaign.
RoleA token acts as the owner or admin who generated it, with their current role. If that person's role is lowered to Viewer, the token can only read.

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 enrollingThe person is already in that campaign, is marked Do not contact, or isn't visible to the token's userExpected. Prosyo protects people from double outreach.
403 with plan_limitEveryone in the request was new, and the monthly import limit is reachedPeople already in Prosyo still update. Wait for next month or upgrade. If only some people fit, the request succeeds and quotaBlocked shows how many weren't added.

See the full API reference.