Docusign swapped the HTTP status codes on several eSignature API errors in 2026: cumulative recipient-limit errors (RECIPIENT_LIMIT_EXCEEDED) now return 400 Bad Request instead of 429 Too Many Requests, while hourly and burst rate-limit errors now return 429 instead of 400. If your retry logic keys off the raw HTTP status code instead of the errorCode field in the response body, this swap flips your backoff and drop-and-alert decisions in exactly the wrong direction.
What did Docusign change about API error status codes in 2026? #
Docusign published the change in Clearer Docusign API error message and status code improvements: 16 eSignature API error messages were rewritten for clarity, and a handful of HTTP status codes were realigned to match what the error actually means. Docusign's own framing is blunt about the reasoning: a 400 means "your request was malformed," a 429 means "you're being throttled, try again later" - and several existing errors had been returning the wrong one of the two for years.
Docusign is explicit that this is a status-code and message-text correction, not a change to any actual limit. The number of recipients you can add, your hourly API quota, and your burst ceiling are all exactly what they were before. What moved is which HTTP status wraps the error.
Which errors flipped from 429 to 400, and which flipped from 400 to 429? #
| Error scenario | errorCode | Old status | New status |
|---|---|---|---|
| Cumulative recipient limit exceeded on an envelope | RECIPIENT_LIMIT_EXCEEDED | 429 | 400 |
| Hourly API invocation limit exceeded | HOURLY_APIINVOCATION_LIMIT_EXCEEDED | 400 | 429 |
| Burst API invocation limit exceeded | BURST_APIINVOCATION_LIMIT_EXCEEDED | 400 | 429 |
| Envelope send limit exceeded (new, split out from billing errors) | ENVELOPE_SEND_LIMIT_EXCEEDED | n/a (new) | 400 |
The logic behind each move, per Docusign's community changelog post: recipient-limit errors are "account-level configuration and transaction composition" problems, not throttling, so they belong in the 400 family. Hourly and burst limits are throttling by definition, so they now return the standard 429 that standard exponential backoff and retry libraries already know how to handle. Docusign also split a new ENVELOPE_SEND_LIMIT_EXCEEDED error code out of what used to be lumped in with billing errors, so send-limit problems no longer masquerade as a payment issue.
Why does a status-code swap break retry logic for Workflow Builder triggers? #
Baton's relay calls the Docusign eSignature API to trigger a Docusign Workflow Builder (formerly Maestro) run after it verifies the inbound webhook's HMAC signature and matches the payload to the workflow's parameters. That trigger call is the exact call this status-code change touches - and any relay, script, or middleware that made a reasonable assumption about status codes a year ago is now wrong in both directions at once.
The naive pattern most integrations wrote (correctly, at the time) looked like this:
if response.status_code == 429:
# throttled, back off and retry
schedule_retry(payload, delay=backoff_seconds)
elif response.status_code == 400:
# bad request, don't retry, alert a human
log_and_alert(payload, response.json())Before the swap, that logic was right: 429 meant "wait and resend," 400 meant "this will never succeed unretried, tell someone." After the swap, the same code does the opposite of what it should:
- A recipient-limit error now arrives as
400. Old logic alerts a human and drops the trigger, which is still arguably fine here since a recipient-limit problem genuinely won't fix itself on retry - but if your alerting logic assumed "400 = malformed payload" and routes it to a different queue or a different message template, the on-call engineer gets a confusing alert about a config limit dressed up as a payload bug. - An hourly or burst rate-limit error now arrives as
429. Some integrations built the assumption directly into the code the other way: "we've only ever seen 400 from rate limits, so treat all 400s as non-retryable." That flips a transient throttle into a dropped, alerted, never-retried trigger - the exact failure mode you built retry logic to prevent.
Docusign's own FAQ names this precisely as the risk: integrations that "explicitly check for a 400 status code on rate limits instead of a 429," or that assume specific limits-related conditions always return one status code or the other, per the community changelog.
How do you check whether your Docusign account already has the new behavior? #
The rollout is staggered by design. Docusign's developer/demo environment already has the new status codes live, and the production rollout is in progress with a stated window of mid-June to early July 2026, per the community post, with Docusign's blog describing the production rollout as expected to complete "in July 2026." Because it's staggered by account, two accounts calling the same endpoint on the same day can legitimately see different status codes right now.
To check your account's current behavior safely:
- In the developer/demo environment, deliberately trigger a cumulative recipient-limit error (send enough envelopes to hit the cumulative cap) and a burst-limit error (fire requests faster than your burst ceiling), then log the full response, status code and body, for both.
- In production, don't force it. Instead, add temporary logging that captures the full response body (not just the status code) on every
400and429you already receive from real traffic, and diff theerrorCodevalues you see against the table above. - Compare what you get against the table. If a
RECIPIENT_LIMIT_EXCEEDEDbody still comes back with429, or anHOURLY_APIINVOCATION_LIMIT_EXCEEDEDbody still comes back with400, your account hasn't rolled over yet - but it will before the window closes, so fix the logic now rather than waiting.
What should a webhook relay's retry logic key off instead of raw status codes? #
The fix Docusign recommends, and the one that survives the next status-code adjustment too, is to stop branching on the HTTP status code and start branching on the errorCode field in the response body. Docusign says it plainly: integrations "that follow best practices (parsing the errorCode attribute)" are unaffected by this entire change, per the community FAQ.
A response body from the eSignature API looks like this regardless of which HTTP status wraps it:
{
"errorCode": "HOURLY_APIINVOCATION_LIMIT_EXCEEDED",
"message": "The maximum number of hourly API invocations has been exceeded."
}Retry logic keyed on that field is immune to the status-code swap:
RETRYABLE_ERROR_CODES = {
"HOURLY_APIINVOCATION_LIMIT_EXCEEDED",
"BURST_APIINVOCATION_LIMIT_EXCEEDED",
}
NON_RETRYABLE_ERROR_CODES = {
"RECIPIENT_LIMIT_EXCEEDED",
"ENVELOPE_SEND_LIMIT_EXCEEDED",
}
body = response.json()
error_code = body.get("errorCode")
if error_code in RETRYABLE_ERROR_CODES:
schedule_retry(payload, delay=backoff_seconds)
elif error_code in NON_RETRYABLE_ERROR_CODES:
log_and_alert(payload, body)
else:
# unknown errorCode - fall back to status code, but flag it for review
handle_unknown(response.status_code, body)This is also the reason a purpose-built webhook relay is worth having in front of Docusign Workflow Builder instead of a status-code if/elif buried in a Zapier step or a one-off script: the relay's job is to own this mapping in one place, so an API-level change like this gets fixed once, not hunted down across every workflow that fires a trigger. If you're wiring up the trigger call yourself, our Docusign API integration guide walks through building that call correctly from scratch, including error handling.
What happens if you leave your integration unchanged? #
Two failure modes, both quiet at first:
- Real throttling gets treated as a hard failure. If your code assumed rate-limit errors only ever came back as
400and routed all400s to "don't retry, alert and drop," a legitimate, temporary hourly or burst limit now looks identical to a permanent bad-request error. The Workflow Builder trigger that should have fired 90 seconds later after backoff instead gets dropped and alerted as broken, even though nothing is actually wrong, it just needed to wait. - Recipient-limit errors get retried forever. If your code assumed
429always meant "retry with backoff" and aRECIPIENT_LIMIT_EXCEEDEDerror now arrives shaped like a throttling problem in your mental model (even though it's actually a400), the more dangerous version of this bug is retrying a request that will never succeed, burning through retry budget, delaying real alerts, and making replaying failed Workflow Builder runs manually necessary once someone notices the backlog.
Neither failure throws an exception. Both look, from a dashboard, like "the integration is a little slower than usual" until someone traces a specific stuck trigger back to a status code that means something different than it used to.
Fix it once, not per workflow #
The safest response to this change isn't a one-time patch for these two error codes, it's moving your retry and alerting logic off raw HTTP status codes entirely and onto errorCode, so the next time Docusign adjusts a status code your integration doesn't notice. If you'd rather not own that mapping logic across every Docusign trigger in your stack, wire your Docusign Workflow Builder triggers up with Baton and let the relay own error handling in one place.