Events and payloads
Every webhook event, its payload schema, and annotated examples.
The envelope
Every delivery wraps its event data in the same envelope:
{
"id": "del_9f2c4e6a",
"event": "job.new",
"version": "2026-04-20",
"createdAt": 1754450123,
"data": { }
}| Field | Notes |
|---|---|
id | The delivery id. Also sent as the X-Lancer-Delivery-Id header. Use it for idempotency. |
event | One of the event names below. Also sent as the X-Lancer-Event header. |
version | The payload schema version, currently 2026-04-20. |
createdAt | UNIX seconds. |
data | Event-specific payload, described per event below. |
Timestamp units differ
The envelope's createdAt is UNIX seconds. Every timestamp inside data.lead (such as
lead.createdAt and lead.datetime) is UNIX milliseconds.
Events
job.new (campaign webhooks)
A suitable job was found for this campaign; your system decides what to do next. Fires only for campaigns in external mode, after the job passes the campaign's saved filters. Lancer's own suitability AI is skipped; deciding is your job.
A complete, realistic payload with every section annotated:
{
"id": "YDDYfNaFpafJMIQCdqC8",
"event": "job.new",
"version": "2026-04-20",
"createdAt": 1776861204, // UNIX seconds
"data": {
"organizationId": "4IumEJtmvJEE2nShMWwM",
"campaignId": "ICdP5Knfizog5IkDRyTV",
"leadId": "ma6H1U9jYesVEzLjzhAc",
// Ready-made action URLs. Call one of them to answer.
"bidUrl": "https://api.lancer.app/v1/campaigns/ICdP5Knfizog5IkDRyTV/leads/ma6H1U9jYesVEzLjzhAc/bid",
"rejectUrl": "https://api.lancer.app/v1/campaigns/ICdP5Knfizog5IkDRyTV/leads/ma6H1U9jYesVEzLjzhAc/reject",
"lead": {
"id": "ma6H1U9jYesVEzLjzhAc",
"createdAt": 1762332662524, // UNIX ms
"campaignId": "ICdP5Knfizog5IkDRyTV",
// Resolved at dispatch time. null if the campaign was deleted
// or otherwise unreadable when the webhook fired.
"campaignName": "Zoho / CRM specialists",
"organizationId": "4IumEJtmvJEE2nShMWwM",
"title": "Customer Support Phone Agent eCommerce | Shopify | Gorgias",
"category": "Customer Service & Tech Support",
"skills": [
{ "name": "Shopify" },
{ "name": "Customer Support" },
{ "name": "Gorgias" }
],
"datetime": 1762332592952, // when the job was posted, UNIX ms
"description": "About Us: We're a fast-growing beauty brand...",
"projectType": "Hiring 4 freelancers",
// "Less than 1 month" | "1 to 3 months" | "3 to 6 months" | "More than 6 months" | null
"projectDuration": "3 to 6 months",
// Your bid must answer these, quoting each question verbatim.
"questions": [
"Are you fine doing email support as well as inbound/outbound call support?",
"Which e-commerce brands did you work for before?"
],
"jobUrl": "https://www.upwork.com/jobs/~021985992773868976101",
"metadata": {
"hours": null,
// "Less than 30 hrs/week" | "More than 30 hrs/week" | null
"duration": "More than 30 hrs/week",
// "Entry level" | "Intermediate" | "Expert"
"experienceLevel": "Expert",
"hourlyRate": { "min": 5, "max": 20 },
// "Hourly" | "Fixed-price"
"paymentType": "Hourly",
"fixedPrice": null
},
"clientInfo": {
"isPaymentVerified": true,
"isPhoneVerified": false,
"rating": 4.79,
"numberOfReviews": 59,
"country": "United States",
"region": "Worldwide",
"jobsPosted": 210,
"totalSpent": 164280.2,
"numberOfHires": 119,
"activeEngagements": 57,
"openJobs": 15, // count of the client's other open jobs
"hireRate": 57,
"memberSince": "2021-06-19T00:00:00.000Z",
"companyIndustry": null,
"companySize": null,
"enterpriseClient": false,
"avgHourlyRatePaid": 16.5,
"companyId": "1406231475600871425"
},
"vendorQualifications": {
// if not null, only freelancers from this location can bid
"location": null,
// "Agency" | "Independent" | "ANY"; if specified, only that type can apply
"talentType": "ANY",
// "FLUENT" | "CONVERSATIONAL" | "NATIVE_OR_BILINGUAL" | "BASIC" | null
"englishLevel": "FLUENT",
// freelancer must have earned more than this (USD) to bid
"minimumEarnings": null,
// freelancers with a Job Success Score below this cannot bid
"jobSuccessScore": 0,
"includeRisingTalent": false
},
"isFeatured": false,
"clientReviews": [],
// "Worldwide" | "USOnly" | "UKOnly"
"region": "Worldwide",
// The Upwork occupation for the job post.
"occupation": "Email, Phone & Chat Support",
// Files the client attached to the job post. Empty array (or null)
// when the client did not attach anything. Each uri is an
// Upwork-relative path; prefix with https://www.upwork.com to
// download. The download requires the freelancer's Upwork session,
// so links typically only work in a browser logged in to Upwork.
"attachments": [
{
"fileName": "Brand_guidelines_v3.pdf",
"length": 532872, // bytes
"uri": "/att/download/openings/2058619791124720878/attachments/cd3c2aea-3602-426c-a16f-d57c0b79f905/download"
}
],
// The client's other currently open postings. Useful for spotting
// clients hiring across multiple roles right now. Distinct from
// clientInfo.openJobs above, which is only a count.
"openJobs": [
{
"id": "1985992773868976101",
// The opaque Upwork cipher; build the job URL as
// https://www.upwork.com/jobs/~02<ciphertext without prefix>
"ciphertext": "~021985992773868976101",
"title": "Shopify storefront redesign",
// "HOURLY" | "FIXED"
"type": "HOURLY",
// Private Talent Cloud posting flag. Most jobs are false.
"isPtcPrivate": false
}
]
}
}
}Respond by calling bidUrl or rejectUrl with your API key. No response means no bid: the lead simply stays parked, there is no timeout fallback.
suitable_job (organization webhooks)
Lancer's suitability AI approved a job for one of your campaigns. Pure notification; Lancer proceeds on its own. data has the same shape as job.new, with data.lead identical to the lead object. For leads that came from an Upwork invitation rather than a campaign, campaignId is null and the action URLs are omitted.
{
"id": "wd_abc123",
"event": "suitable_job",
"version": "2026-04-20",
"createdAt": 1713580800,
"data": {
"organizationId": "org_...",
"campaignId": "cmp_...",
"leadId": "lead_...",
"bidUrl": "https://api.lancer.app/v1/campaigns/cmp_.../leads/lead_.../bid",
"rejectUrl": "https://api.lancer.app/v1/campaigns/cmp_.../leads/lead_.../reject",
"lead": { /* the lead object */ }
}
}unsuitable_job (organization webhooks)
Lancer analyzed a job and decided it does not match the campaign. Useful for auditing what the AI is filtering out.
{
"data": {
"organizationId": "org_123",
"campaignId": "cmp_123",
"leadId": "lead_abc123",
"lead": { "...": "the lead object" },
"suitabilityRating": 3,
"suitabilityReason": "The job requires on-site presence in Berlin, which conflicts with..."
}
}bid_complete (organization webhooks)
A proposal was successfully submitted on Upwork. This is the confirmation that a bid (automatic, or one you submitted via the API) actually went through.
{
"id": "wd_xyz456",
"event": "bid_complete",
"version": "2026-04-20",
"createdAt": 1713580900,
"data": {
"organizationId": "org_...",
"campaignId": "cmp_...",
"leadId": "lead_...",
"lead": { /* the lead object */ },
// The cover letter that was actually submitted.
"proposal": "Hi, I noticed you are looking for..."
}
}Dry-run bids via the API fire a simulated bid_complete so you can test end to end.
lead.new (organization webhooks)
A new message room appeared in your Upwork inbox, usually a client replying to a proposal or inviting you directly. Fires once per room, at creation. Later messages in the same room do not fire again.
{
"id": "wd_lead_new_001",
"event": "lead.new",
"version": "2026-04-20",
"createdAt": 1776861204,
"data": {
"organizationId": "4IumEJtmvJEE2nShMWwM",
// The Upwork bidder account whose inbox received the room.
// Useful if you have multiple accounts connected, so you can
// route the event to the right workflow per account.
"accountId": "TlMAG4bpU31hZOQ5ZkLz",
// The Lancer campaign this room is tied to, enriched with the
// campaign name so you do not need a second API call. Only
// populated when the room is a reply to a proposal you submitted
// via a Lancer campaign (typically roomTypeExtended "fl-proposal"
// with a resolvable room.jobClientHistory.campaignId).
// null for cold invitations, direct-hire offers, and DMs.
"campaign": {
"id": "ICdP5Knfizog5IkDRyTV",
"name": "Zoho / CRM specialists"
},
"room": {
"id": "room_9f3c41d0a28b4e2c9d5f81b07e6a3145",
"roomId": "room_9f3c41d0a28b4e2c9d5f81b07e6a3145",
// The human-readable name Upwork uses for this thread.
// Usually the client's name, sometimes with a company suffix.
"roomName": "Jane Doe, Acme Co",
// Upwork's subject line for the thread. Empty for pure DMs.
"topic": "Zoho implementation",
// What kind of inbox entry this is.
// "client-invitation": client invited you to apply
// "direct_hire_FL_profile": client sent a direct-hire offer
// from your profile (no prior bid)
// "fl-proposal": room created because YOU submitted
// a proposal and the client replied.
// This is the only variant where
// data.campaign is typically set.
// "direct_message": generic DM, no job attached
"roomTypeExtended": "fl-proposal",
// Unix ms. The moment of the latest story in this room on
// Upwork's side (i.e. the newest message).
"recentTimestamp": 1776861180000,
"numUnread": 1,
"latestStory": {
"storyId": "story_a12b34c56d78e90f",
"created": 1776861180000,
"updated": 1776861180000,
"deleted": 0,
"isSystemStory": 0,
"blockedTimestamp": -1,
"userId": "2000000000000000001",
// The first line of the incoming message. Note Upwork prefixes
// with the sender's first name (e.g. "Jane: ...") or "You: ..."
// when the story is yours.
"message": "Jane: Hi, we'd love to chat about our Zoho rollout..."
},
// Opaque Upwork context. Field set varies by roomTypeExtended.
// Anything can be absent; treat every key as optional.
"context": {
"jobName": "Zoho implementation",
"amount": "$15.0/hr",
"associatedAgency": "Your Agency",
"associatedAgencyOrgId": "1055837229072420865",
"clientId": "1587051781037477888",
"clientOrgId": "1587051781037477889",
"clientName": "Jane Doe",
"jobPosterName": "Jane Doe",
"jobTitle": "Zoho implementation",
"jobUid": "2034987699281050049",
"applicationUid": "2034988290957822635",
"applicantName": "Your Name",
"freelancerName": "Your Name",
"freelancerId": "1055837229068226560",
"freelancerOrgId": "1055837229072420865",
// "Activated" | "new" | "withdrawn" | "CANCELLED" | "accepted"
// | "expired" | ... (Upwork's application-status string).
"currentStatus": "Activated",
"showAsAgency": "false"
},
// Present when Lancer has previously seen the underlying Upwork
// job (e.g. you bid on it via a campaign). Null-ish fields mean
// Lancer does not have that data. campaignId links the room back
// to the Lancer campaign that bid; null if there is no match.
// When non-null, the top-level data.campaign above mirrors it
// with the resolved campaign name.
"jobClientHistory": {
"jobUrl": "https://www.upwork.com/jobs/~022034987699281050049",
"title": "Zoho implementation",
"campaignId": "ICdP5Knfizog5IkDRyTV",
"biddingAmount": null,
"boostingAmount": null,
"boostedForPlace": null,
"biddedAt": null,
"jobDatetime": null,
// Same shape as the lead object's clientInfo, where available.
"clientInfo": {
"isPaymentVerified": true,
"isPhoneVerified": true,
"rating": 5,
"numberOfReviews": 3,
"country": "USA",
"region": "Worldwide",
"jobsPosted": 17,
"totalSpent": 3458.21,
"numberOfHires": 7,
"activeEngagements": 4,
"openJobs": 5,
"hireRate": 42,
"memberSince": "2022-10-31T00:00:00.000Z",
"companyIndustry": null,
"companySize": null,
"enterpriseClient": false,
"avgHourlyRatePaid": 20.7,
"companyId": "1587051781037477889"
}
}
}
}
}Subscribing to this event requires a bidder account connected to chat.
The lead object
The lead in job.new, suitable_job, unsuitable_job, and bid_complete payloads, and in GET /leads responses, has this shape. Fields that Upwork does not provide for a given job are null.
| Field | Type | Notes |
|---|---|---|
id | string | Stable lead id. Use it with the Public API. |
createdAt | number | When Lancer created the lead, ms. |
title | string | Job title. |
description | string | Full job description. |
jobUrl | string | Link to the job on Upwork. |
datetime | number | When the job was posted, ms. |
category | string | Upwork category. |
occupation | string | Upwork occupation. |
skills | array | { "name": string } per skill. |
questions | string[] | Screening questions. Answers must match these verbatim when bidding. |
projectType, projectDuration | string | Project shape. |
isFeatured | boolean | Featured job flag. |
region | string | Worldwide, USOnly, or UKOnly. |
metadata | object | experienceLevel, paymentType, hourlyRate ({min,max} or null), fixedPrice, duration, hours. |
clientInfo | object | Payment and phone verification, rating, numberOfReviews, country, region, totalSpent, numberOfHires, jobsPosted, hireRate, memberSince, companyIndustry, companySize, enterpriseClient, avgHourlyRatePaid, activeEngagements, openJobs (count), and more. |
clientReviews | array | The client's past reviews: freelancer feedback both ways, ratings, billed amounts. |
vendorQualifications | object | What the client asks of freelancers: location, talentType, English level, minimum earnings, job success score, and similar. |
attachments | array or null | Files on the job post: { fileName, length, uri }. uri is relative; prefix https://www.upwork.com. |
openJobs | array or null | The client's other open postings: { id, ciphertext, title, type, isPtcPrivate }. |
campaignId | string | The matching campaign. |
campaignName | string or null | Its name. |
organizationId | string | Your organization. |
proposalId | string | Set once a proposal exists. |
Anything not listed here is internal and never included.