Lever's native Docusign integration is good at exactly one thing: sending an offer letter when a recruiter clicks "Send offer". If you want anything else - a background-check NDA on candidateStageChange, an internal-mobility addendum on rehire, a compliance form for international hires when archived to a specific reason - you have to wire it up yourself.

This guide shows how to do that with Baton sitting between Lever's webhooks and a Docusign Workflow Builder workflow. No glue code, no field-mapping UI to learn, no OAuth into Lever.

What this article assumes you already have set up #

Before you start, you should have:

  • A Lever account with Super Admin or Admin access to Settings -> Integrations and API -> Webhooks. Without admin scope, you cannot see the signature token or configure event subscriptions.
  • A Docusign account with IAM and at least one published Workflow Builder workflow. You need the workflow's trigger URL, which you get from the getWorkflowTriggerRequirements endpoint or from the workflow detail screen.
  • Your Docusign workflow's input parameter names already defined. Baton matches Lever payload fields to those parameter names by name, so the parameters dictate your mapping. More on that below.
  • A Baton account connected to your Docusign tenant. If you've followed the Greenhouse tutorial, the setup is identical on the Docusign side.

If you have all four, the rest of this guide is configuration, not engineering.

Which Lever events make sense as Docusign triggers #

Lever exposes a small set of webhook events that can be configured in the UI. The four you'll actually use for agreement automation are:

Lever's full event taxonomy also includes interview lifecycle and contact events, but those are not user-configurable from the Webhooks tab and are usually not the right shape for an agreement trigger (Lever docs).

Match the event to the agreement you want signed:

Lever eventTypical Docusign workflow
candidateHiredOffer letter (if you've moved past Lever's native flow), NDA, employee handbook acknowledgement
candidateStageChange to a "Reference check" stageReference-check release authorization
candidateStageChange to a "Background check" stageBackground-check consent form
applicationCreated on a regulated job postingEEO/voluntary self-id form, GDPR data-processing consent
candidateArchiveChange with reason WithdrewNothing - this is your signal to cancel an in-flight envelope, not to send one

A common mistake: subscribing to candidateStageChange and treating every stage transition as a trigger. The webhook fires on every move, including backwards moves and intermediate states. Always filter on toStageId (or the corresponding human-readable stage name) inside your Workflow Builder workflow's conditional logic, not at the relay layer.

Configuring a Lever webhook to point at Baton #

In Baton, create a new Action wired to your target Workflow Builder workflow. Baton generates a unique inbound webhook URL for that Action. Copy it.

In Lever:

  1. Go to Settings -> Integrations and API -> Webhooks.
  2. Find the event you want (for example, Candidate hired).
  3. Toggle it on, paste the Baton URL into the Webhook URL field, and save.
  4. Copy the Signature token Lever displays for that event. You'll paste it into the Baton Action so Baton can verify incoming requests.

Lever signs each webhook with an HMAC-SHA256 hash of the token value concatenated with the triggeredAt timestamp, using your signature token as the key (Lever API docs). Baton verifies that signature on every inbound request - reject any payload that does not match.

For reference, a Lever candidateStageChange payload looks like this:

{
  "triggeredAt": 1431454330072,
  "event": "candidateStageChange",
  "signature": "m3e7...",
  "token": "0c54...",
  "data": {
    "opportunityId": "3410c8b9-5c31-4bab-b7e9-9f710206d647",
    "candidateId": "daeaa038-cddb-4d69-9b8c-74b6c23be3f3",
    "contactId": "05c248f9-ccb1-435f-bab5-0ec608aec263",
    "toStageId": "7c2690d8-6308-4ed9-ae2a-cbfe7db26593",
    "fromStageId": "992f01a9-ee19-41d2-ae79-..."
  }
}

The shape is the same across event types - what changes is event and the contents of data (Lever API docs).

Mapping Lever payload fields to Workflow Builder parameters #

Baton doesn't have a drag-and-drop field-mapping UI. It matches incoming JSON fields to your Workflow Builder workflow's parameter names by name. So the trick is to name your Workflow Builder parameters the same as the Lever fields you want to consume.

A pragmatic Workflow Builder parameter schema for an candidateHired workflow:

parameters:
  - name: candidateId
    type: string
  - name: opportunityId
    type: string
  - name: contactId
    type: string
  - name: candidateName       # fetched by your workflow step from Lever Data API
    type: string
  - name: candidateEmail      # same
    type: string

candidateId, opportunityId, and contactId come straight from data.* in the webhook. candidateName and candidateEmail are not in the webhook payload - the Lever webhook only ships IDs. Two ways to get the rest:

  1. Inside Workflow Builder, add an HTTP step that calls GET https://api.lever.co/v1/opportunities/{opportunityId} and pulls the candidate's name and emails[0]. This keeps the credential boundary clean: Docusign holds the Lever API key, Baton doesn't.
  2. In Lever, configure the webhook to include hydrated candidate data if your plan supports it.

Option 1 is what we recommend. Baton's design choice is to never hold source-platform credentials - it has webhook URLs only, and OAuth is reserved for Docusign.

Handling candidate-stage edge cases #

Three Lever situations regularly break naive integrations.

Rescinded offers. A recruiter marks the candidate as hired (your Docusign workflow fires), then HR rescinds. There's no Lever event called offerRescinded. The signal is a candidateArchiveChange to an archive reason like Offer declined or Position closed. Subscribe to it and have Workflow Builder void any open envelope for that opportunityId.

Withdrew during onboarding. Same pattern: candidateArchiveChange with reason Withdrew. Treat this as a cancel signal, not a send signal.

Rehires. Lever can return a previously archived candidate to an active stage via candidateStageChange. Your workflow will fire again on the same candidateId. Use idempotency keyed on opportunityId + event + triggeredAt so a re-fire of the same logical event doesn't send a duplicate envelope. If you need a primer on this pattern, see our silent failures piece - the same principles apply on the inbound side.

What to do when an offer letter does not fire #

You toggle the Lever webhook on, move a test candidate to Hired, and... nothing in Docusign. The triage order:

  1. Check Baton's Action log first. Did the request arrive? If yes, did it match a workflow? Baton logs the inbound payload and the matched (or missed) parameter resolution.
  2. If the request never arrived, check Lever's Webhooks panel - Lever shows recent delivery attempts and HTTP response codes inline. Lever expects your endpoint to return a 2xx; anything else is treated as failure (Lever API docs).
  3. If the request arrived but no workflow ran, the most common cause is parameter-name mismatch. Baton's matcher is case-sensitive against the Workflow Builder schema. candidateId is not CandidateID.
  4. If the workflow started but no envelope was created, that's a Docusign side problem, not a webhook problem. Open the Workflow Builder run history and look for a failed step. Baton's responsibility ends once the workflow is triggered; envelope creation is the workflow's job.

This last point is worth repeating because it confuses people: Baton does not trigger Docusign envelopes. It triggers Workflow Builder workflows, and those workflows create envelopes. When troubleshooting, separate the two failure domains.

Where Lever stops and Workflow Builder takes over #

Two reminders before you ship this to production:

Baton does not write back to Lever. Signed offer letter? Lever will not know about it via Baton. If you need status back in Lever (for example, marking the candidate's offer as "Signed"), build that on the Docusign side with a Workflow Builder step that calls Lever's API after the envelope completes. Same pattern we describe in the Coupa walkthrough for ERP write-backs.

Lever's native Docusign integration is still useful if your only need is the offer letter and your offer template fits within Lever's offer template format (which requires a {candidate_signature} placeholder and a max 25 MB template file) (Lever Support). The webhook-relay pattern is what you reach for when you've outgrown that, or when offers are only one of several agreements you need to send out of Lever.

If you're standing this up and hit a wall, the Action log and Lever's delivery panel give you everything you need - in that order.