Delivery, security, and testing

Headers, the security model, failure behavior, and how to test deliveries.

Request headers

Every delivery is an HTTPS POST with:

HeaderValue
Content-Typeapplication/json
X-Lancer-EventThe event name, for example job.new.
X-Lancer-Delivery-IdUnique delivery id, equal to the payload's id. Deduplicate on this.

Security model: the URL is the secret

Deliveries are not signed. There is no HMAC header and no shared signing secret. The webhook URL itself is the credential: anyone who knows it can send you requests that look like Lancer's.

Practical guidance:

  • Put an unguessable path segment in your URL, for example https://api.example.com/hooks/lancer/f3a9c1d7e5b2.
  • Treat the URL like a password: keep it out of client-side code, logs, and repositories.
  • If the URL leaks, update the webhook to a new URL in the web app.
  • Optionally verify that referenced ids (campaign, lead) are real by calling the Public API before acting on a payload. A forged payload cannot make GET /leads return a lead that does not exist.

Delivery semantics: at-most-once, no retries

  • One attempt per delivery. There is no automatic retry.
  • Timeout is 10 seconds. Success is any 2xx response.
  • Failures never disable the webhook. A non-2xx, timeout, or connection error marks that delivery failed in the log, and the next event is delivered normally. Fix your endpoint and the stream continues on its own; there is nothing to resume.
  • No ordering guarantee. Deliveries are dispatched independently; do not assume event order.
  • Fan-out. Every enabled webhook subscribed to an event gets its own delivery; one endpoint failing does not affect others.

Design your receiver accordingly:

1. Validate the JSON parses and X-Lancer-Event is one you expect.
2. Store the payload keyed by X-Lancer-Delivery-Id (skip if already seen).
3. Respond 200 immediately.
4. Process asynchronously.

Missing a delivery is recoverable for the control flow: in external mode you can re-trigger any lead's job.new from the campaign Feed, and GET /leads always shows current matching jobs.

Delivery log

Every attempt is recorded and visible in the web app:

  • Organization webhooks: Settings, then Integrations, Activity tab.
  • Campaign webhooks: the campaign's Integrations tab.

Each record shows the event, the target URL, the HTTP status your endpoint returned, and the first part of the response body, which makes endpoint debugging straightforward.

Testing

Three ways to test, from safest to most end-to-end:

  1. Test button: next to any webhook in the web app. Sends a genuine payload built from a recent real lead (for lead.new, a synthetic room). If the organization has no leads yet, the test asks you to run a campaign once first.
  2. Per-lead trigger (external mode): every lead card on the campaign Feed has a Trigger button that re-dispatches job.new for that lead. Useful while developing your receiver.
  3. Dry-run bid: call /bid with dryRun: true to exercise your full loop, including a simulated bid_complete, without touching Upwork.

For local development, expose your receiver with a tunnel such as ngrok or cloudflared and use the tunnel URL as the webhook URL.

On this page