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": { }
}
FieldNotes
idThe delivery id. Also sent as the X-Lancer-Delivery-Id header. Use it for idempotency.
eventOne of the event names below. Also sent as the X-Lancer-Event header.
versionThe payload schema version, currently 2026-04-20.
createdAtUNIX seconds.
dataEvent-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.

FieldTypeNotes
idstringStable lead id. Use it with the Public API.
createdAtnumberWhen Lancer created the lead, ms.
titlestringJob title.
descriptionstringFull job description.
jobUrlstringLink to the job on Upwork.
datetimenumberWhen the job was posted, ms.
categorystringUpwork category.
occupationstringUpwork occupation.
skillsarray{ "name": string } per skill.
questionsstring[]Screening questions. Answers must match these verbatim when bidding.
projectType, projectDurationstringProject shape.
isFeaturedbooleanFeatured job flag.
regionstringWorldwide, USOnly, or UKOnly.
metadataobjectexperienceLevel, paymentType, hourlyRate ({min,max} or null), fixedPrice, duration, hours.
clientInfoobjectPayment and phone verification, rating, numberOfReviews, country, region, totalSpent, numberOfHires, jobsPosted, hireRate, memberSince, companyIndustry, companySize, enterpriseClient, avgHourlyRatePaid, activeEngagements, openJobs (count), and more.
clientReviewsarrayThe client's past reviews: freelancer feedback both ways, ratings, billed amounts.
vendorQualificationsobjectWhat the client asks of freelancers: location, talentType, English level, minimum earnings, job success score, and similar.
attachmentsarray or nullFiles on the job post: { fileName, length, uri }. uri is relative; prefix https://www.upwork.com.
openJobsarray or nullThe client's other open postings: { id, ciphertext, title, type, isPtcPrivate }.
campaignIdstringThe matching campaign.
campaignNamestring or nullIts name.
organizationIdstringYour organization.
proposalIdstringSet once a proposal exists.

Anything not listed here is internal and never included.

On this page