Error handling and idempotency in e-signature APIs
Good error handling in an e-signature integration means distinguishing between failures worth retrying (a network timeout, a rate limit) and ones that aren't (a malformed request, an invalid API key), and designing your integration so a network hiccup or a double-click doesn't silently create a duplicate signature request or leave your own system out of sync with what actually happened.
The error categories that matter
- Client errors (4xx): something about the request itself was wrong — a missing required field, an invalid document ID, an expired or revoked API key. Retrying the exact same request won't fix this; you need to correct the request first.
- Rate limiting (429): you've sent more requests than the account's limit allows in the current window. This is retryable, but only after waiting — retrying immediately just gets rate-limited again. See Rate limits and best practices for e-signature APIs.
- Server errors (5xx): something went wrong on the platform's side. These are generally safe to retry, ideally with a short delay and a cap on how many times you'll try before giving up and surfacing the failure.
- Network-level failures: a timeout or connection error where you genuinely don't know whether the request was received and processed before the connection dropped. This is the trickiest category, and it's where idempotency matters most.
Why "did it actually go through?" is the hard problem
If a request to create a signature request times out, you don't actually know whether it succeeded on the server before the response was lost, or failed before reaching it. Retrying blindly risks creating a second, duplicate signature request for a successful call you simply didn't get confirmation of.
What Esignapi's API does about duplicate request creation
It's worth being precise here rather than assuming a specific mechanism exists: Esignapi's request-creation endpoint includes an internal, best-effort safeguard against the most common real-world cause of duplicates — a double-click or accidental double-submit from the same account, for the same document, within a short window of each other. This is a safety net against that specific case, not a general-purpose, client-controlled idempotency key system of the kind some APIs expose (where you generate your own unique key per logical operation and pass it with the request so retries of that exact key are guaranteed to be treated as the same operation). If you're building an integration where avoiding duplicate requests under retry is important, don't assume you can pass your own idempotency key to the request-creation endpoint — instead, build that guarantee on your own side.
How to build your own duplicate protection
Since you can't rely on passing your own idempotency key to the API, the more robust approach is to track the operation on your own side before calling the API:
- Before calling the API, record in your own database that you're about to create a signature request for a given logical operation (an order ID, a workflow step), with a status of "pending."
- Make the API call. If it succeeds, record the returned request ID against that same logical operation and mark it "done."
- Before retrying after a failure or timeout, check your own record first — if it's already marked "done," don't call the API again; if it's "pending" from a previous attempt, check the request's actual status via the API (or your own stored data) before deciding whether to retry the creation call.
This pushes the idempotency guarantee into your own system, which is something you control completely, rather than assuming the API provides it for you.
Handling errors from document upload separately
Upload failures are usually more straightforward to retry safely than request creation, since re-uploading the same file content doesn't typically create a confusing partial state the way a duplicated signature request would — but it's still worth checking the response status explicitly rather than assuming success, since a failed upload means any immediately following request-creation call would reference a document that doesn't actually exist.
Designing for webhook-driven updates safely
If your integration relies on webhooks to learn when a signer completes a document, make sure your webhook handler is itself idempotent — processing the same event twice (which can happen for reasons unrelated to any retry policy, such as your own infrastructure redelivering a queued message) shouldn't result in duplicate side effects like sending a duplicate confirmation email. See E-signature webhooks explained for more on this, including why webhook delivery itself isn't automatically retried.
A simple retry strategy for transient errors
For 5xx errors and network failures specifically, a short exponential backoff (wait briefly, retry, wait longer if it fails again, retry, give up after a small number of attempts) is a reasonable default. Don't apply that same strategy to 4xx errors — retrying a request that's malformed in some way will just fail the same way again until the actual problem is fixed.
Logging enough to debug issues later
Log the request ID (once you have one), the HTTP status code, and the error message for every failed call, not just a generic "something went wrong." When you're debugging an integration issue days later, having the actual error response on hand saves significant time compared to trying to reproduce the failure from scratch.
A worked example of the pattern
Concretely, if your application creates a signature request as part of processing a customer order, your own orders table might gain a column tracking the signature state for that order: null (not yet attempted), the Esignapi request ID once creation succeeds, or a specific error marker if it failed in a way that needs attention. Before attempting to create a request for a given order, check that column first — if it already holds a request ID, you know the operation already succeeded and there's nothing left to do, regardless of whether the response to the original call was ever received by your code.
Separating "the API call failed" from "the business operation failed"
It's worth keeping these conceptually distinct in your error handling: an API call can fail for purely technical reasons (a timeout, a 500, a rate limit) that have nothing to do with whether the underlying business operation — getting this specific document signed — should ultimately succeed. A technical failure is usually worth retrying; discovering that the document itself is invalid, or that a signer's email is malformed, is a different kind of failure that retrying won't fix, and surfacing it clearly to whoever's responsible for correcting it (a person on your team, or the end customer) is the more useful response.
Build a resilient integration
Esignapi's API documentation covers the specific error responses and status codes you'll encounter for every endpoint, so you can build precise handling rather than guessing at failure modes.
Frequently asked questions
Can I pass my own idempotency key when creating a signature request?
No — the request-creation endpoint doesn't accept a client-supplied idempotency key. It has an internal safeguard against accidental double-submission within a short window, but for a reliable guarantee against duplicates under retry, track the operation on your own side as described above.
None, strictly speaking — even a 5xx error is better retried with a short delay than immediately, and a 429 specifically requires waiting before retrying at all.
Should I retry a failed document upload the same way I'd retry a request creation call?
You can generally retry an upload more freely, since re-uploading the same file content doesn't create the kind of confusing duplicate state that re-creating a signature request would.
How many times should I retry before giving up?
There's no universal answer, but a small, capped number of attempts (so a genuinely broken request doesn't retry forever) with increasing delay between attempts is a reasonable default for most integrations.
Is it safe to assume a timeout means the request failed?
No — a timeout only tells you that you didn't receive a response in time, not whether the operation actually completed on the server. Checking the actual state (via a status lookup) before retrying is safer than assuming either outcome.