To void and resend a signature request safely through an API, treat the correction as a durable, tenant-scoped operation with its own state machine, not as two HTTP calls. Bind the operation to a hash of the approved replacement payload. Claim it atomically. Confirm the void against authoritative provider state before you create the replacement. When a call times out, reconcile first and never retry blindly.
The reason is simple. Once a request is out, a person on the other side may sign at any second, and your system does not control that clock. A correction is therefore a race between your intent and the signer's action, spread across three actors: your application, the eSignature provider and the signer. Most duplicate-contract incidents and most wrong-version-executed incidents begin in that race.
This guide is written for backend and platform engineers who own the code that sends, corrects and re-sends signature requests. It describes a proposed design, which we call a Supersession Ledger. It is a pattern we are proposing for you to evaluate, not an established eSignly product feature or a method eSignly claims to run in production. Where we discuss eSignly capabilities, we limit ourselves to what the public eSignly pages state, and we tell you what to verify in the current API reference. All code is illustrative.
If you are already handling timeouts and webhooks, this article builds on those topics. See our companion pieces on reconcile-first recovery for API timeouts and webhook failure handling with dead-letter replay. Here, the focus is narrower: what happens when the document itself has to change after it has been sent.
Key Takeaways
- A correction is a business operation, not an HTTP call. Give it a durable identity, a state machine and an owner.
- Bind the operation key to an approved payload hash, scoped by tenant. Reject any reuse of a key with a changed payload.
- Never create the replacement until the void is confirmed. Two live requests for one agreement is the failure you are designing against.
- Unknown means unknown. A timed-out void is neither success nor failure. Read authoritative state, then decide.
- Plan for losing the race. If the signer completes first, the original stands and the outcome needs a named human owner, not a silent retry.
- Use go/no-go controls, not a score. Every control in the checklist below is required before you rely on automated corrections.
Why Changing a Document That Is Already Out for Signature Is a Distributed-Systems Problem
Key takeaway: The hard part is not the void call. It is that the signer, the provider and your database each hold a different version of the truth for a short but real window of time.
Teams usually discover corrections late. A pricing error appears in a quote, a legal entity name is wrong, or a counterparty asks for a changed clause after the request is already in their inbox. The product requirement sounds trivial: cancel the old one, send a new one. The engineering reality has three independent actors:
- Your application decides the document must change and holds the approved replacement.
- The eSignature provider holds the authoritative state of the request: pending, viewed, partially signed, completed, declined, voided or expired.
- The signer acts on their own schedule and can complete the original while your void call is in flight.
None of these share a transaction. That means you cannot make "void the old request and create the new one" atomic across systems. You can only make it recoverable: every intermediate state is recorded, every external call has an identity, and every ambiguous result is resolved by reading authoritative state.
Three kinds of change, three different rules
Not every change is a correction of the same kind. Mixing them is one of the most common design errors.
| Situation | Typical handling | Main risk | Who decides |
|---|---|---|---|
| A. No signer has acted yet | Void the request, send a replacement | Duplicate live requests; signer confusion | Business owner approves replacement payload |
| B. Some signers have signed (multi-party) | Usually a new request with a recorded reason; treat prior signatures per your policy | Signatures attached to a text that no longer matches the deal | Legal or contract owner |
| C. Request already completed | Not a correction. A new amendment or addendum process | Treating a completed record as editable | Legal counsel and contract owner |
Only case A is safe to automate end to end. Case B should be automated only up to the point of creating a reviewable work item. Case C should never go through the void path at all. Whether a given amendment is enforceable, and how superseded records must be retained, depends on your jurisdiction, contract type and industry, so confirm with counsel. For the U.S. baseline on electronic signatures and records, the federal ESIGN Act is codified at 15 U.S.C. 7001. It addresses legal effect and consent for electronic records. It does not tell you how to engineer a correction flow.
A measurement worth owning: the supersession race window
We propose one metric that you can measure from your own logs: the supersession race window, the time between the moment your system decides to void and the moment the provider confirms the void. Every second in that window is a second in which the signer can complete the original. Your own data, not a benchmark, tells you how large it is. If your void path includes a queue, a retry backoff and a confirmation poll, the window may be minutes rather than milliseconds. That is exactly why the "lost the race" outcome must be a first-class state in your design.
For a deeper look at how state drifts between your system and the provider, see our guide to resilient eSignature workflows and state sync.
Common Failure Patterns: How Most Teams Handle Corrections, and Why It Breaks
Key takeaway: Capable teams still ship fragile correction flows because each component passes its own tests. The failures live in the gaps between components, where nobody owns the invariant.
The four approaches you will usually find
| Approach | How it works | Where it breaks |
|---|---|---|
| Edit in place | Change the source document and hope the provider sees it | Signer may view or sign a different version than your record shows. Evidence trail no longer matches the contract. |
| Fire-and-forget void then create | Call void, then immediately call create | If void fails or times out, two live requests exist. If create fails, the customer has no request at all. |
| Generic retry wrapper | HTTP client retries on timeout or 5xx | Retries a mutation whose first attempt may have succeeded. Creates duplicates or double-voids. |
| Manual dashboard fix | Support staff void and resend in the provider console | Your database never learns it happened. State diverges silently. |
Failure scenario 1: the retried void that created a second live request (illustrative)
A platform team wraps its provider client with a generic retry policy. A sales operations user corrects a price on a contract. The void call times out at the load balancer. The wrapper retries, the retry also times out, and the job gives up. A fallback step, written for the "void failed, so proceed with replacement anyway" case, creates the new request. In reality the first void had succeeded, so the replacement is the only live request and everything looks fine. On another day the same sequence plays out with a void that never reached the provider. Now two live requests exist, the signer receives both, and signs the older one first.
Nothing here was careless. The retry wrapper was reviewed. The fallback was a reasonable product decision in isolation. The gap was that no one owned the question "what is the authoritative state of the original request?" before moving on.
Failure scenario 2: the late completion event on a superseded request (illustrative)
A CRM listens for a completion webhook and marks the opportunity "contract executed". A correction is in progress: the replacement has been sent, but the void on the original was still pending. The signer completes the original. The webhook arrives, the handler looks up the opportunity by customer, and marks it executed, attaching the signed PDF of the superseded text. The replacement is later also signed. The record now claims one execution but holds two signed documents with different terms.
The handler was correct for the normal path. It was never taught that a request can be superseded, so it keyed on the customer instead of on the specific request reference and revision.
Failure scenario 3: the idempotency key that lied (illustrative)
A team adds a client-generated key, correction-1042, to make corrections safe to retry. Two problems follow. First, the key is unique per customer in the code, but the table is shared across tenants, so a different tenant's correction-1042 returns the first tenant's stored result. Second, an operator edits the replacement text and resubmits with the same key. The system sees a known key, returns the earlier result, and the corrected text is never sent. Both failures are silent because a replay looks like success.
Why intelligent teams still fail here
- Ownership gaps. Product owns "correct a contract", platform owns the HTTP client, legal owns retention. Nobody owns the end-to-end invariant: at most one live request per agreement revision.
- Happy-path testing. Integration tests mock the provider as always responding. The timeout-after-success case is rare in testing and common in production.
- Unknown treated as failure. A timeout is logged as an error and routed to the failure branch, which is exactly the wrong branch half the time.
- Convenience paths outside the system. Support uses the provider dashboard because it is faster, and your database cannot see it.
Illustrative arithmetic, not a benchmark: suppose a platform sends 2,000 signature requests a day and 1% need correction. That is 20 corrections a day, or about 7,300 a year. If 2% of correction calls end in an ambiguous result, that is roughly 146 ambiguous outcomes a year, each of which is a potential duplicate live request or a lost one. Replace these numbers with your own measurements before you size the work.
A Proposed Design: The Supersession Ledger
Key takeaway: Record the correction as a durable operation before touching the provider. Move it through explicit states. Make every ambiguous state resolvable by reading authoritative status.
The Supersession Ledger is a proposed pattern, and you should evaluate it against your own architecture. It consists of a single table of operations, a small state machine, and a handful of invariants. It does not require provider features beyond those you can verify in the provider's API reference, though it benefits greatly from them.
The six invariants
- Tenant-scoped durable identity. An operation is identified by
(tenant_id, operation_key). The key never means anything outside its tenant. - Immutable approved payload. The operation stores a hash of the canonical approved replacement payload (document revision, signers, fields, message). The payload cannot change after approval. A changed payload gets a new operation.
- Changed-payload reuse is rejected. The same key with a different hash returns a conflict. The same key with the same hash returns the current state as a replay, not as new work.
- Atomic claim. One worker at a time holds a lease on an operation. Claiming is a single conditional update, not a read followed by a write.
- One active operation per document. A database constraint, not application code, prevents two active operations on the same document at once.
- Intent before action, confirmation before progress. The intent to call the provider is committed first. The next step runs only after authoritative confirmation.
The state machine
| State | Meaning | Allowed next states | Who acts next |
|---|---|---|---|
| accepted | Operation recorded with approved payload hash; work scheduled | void_in_flight | Worker |
| void_in_flight | Intent to void committed; call outcome not yet known | void_confirmed, void_unknown, blocked_signed, terminal_failed | Worker |
| void_unknown | Void call outcome could not be confirmed | void_confirmed, blocked_signed, void_in_flight (after read shows still pending), terminal_failed | Reconciler |
| void_confirmed | Provider state shows original voided | replacement_in_flight | Worker |
| replacement_in_flight | Intent to create replacement committed | completed, replacement_unknown, terminal_failed | Worker |
| replacement_unknown | Create call outcome could not be confirmed | completed, replacement_in_flight (only if absence is verified), terminal_failed | Reconciler |
| blocked_signed | Signer completed the original first. Original stands. | (terminal; human decision on any amendment) | Named owner |
| completed | Original voided, replacement live and recorded | (terminal) | None |
| terminal_failed | Retry budget exhausted or unrecoverable error | (terminal; human review) | Named owner |
Notice the two states that most designs lack: blocked_signed, which is the lost race, and the two _unknown states, which hold the meaning "completion could not be confirmed". An unknown is not a success and it is not a verified failure. It is a debt that the reconciler must pay by reading authoritative state.
Distinguishing a duplicate from a legitimate later operation
Legitimate second corrections happen: the first replacement is itself wrong. The design handles this without ambiguity. The operation key is derived from the business intent, for example the document reference plus the base revision being superseded. A later correction has a higher base revision, so it gets a new key and a new payload hash. A retry of the same intent has the same base revision and the same hash, so it replays. A request with the same key but a different hash is a conflict and is rejected. The one-active-operation constraint ensures the second correction cannot start until the first reaches a terminal state.
The correlation problem for replacements
The hardest unknown to resolve is replacement_unknown: did the create call succeed? You can only answer that if you can find the replacement on the provider side. That requires one of three things: a provider-supported idempotency mechanism on create, a client reference field you can search on, or a status listing you can filter reliably. If none exist, say so in your design document. In that case, an unknown replacement must go to a human rather than be retried, because a blind retry may create a second live request.
Implementation Sketch: Atomic Claims, Payload Hashes and Reconcile-First Execution
Key takeaway: Use your own database for true compare-and-swap, and use provider-side conditions only where the provider documents them. Where it does not, disclose the remaining race instead of pretending it is closed.
The code below is illustrative. It uses PostgreSQL syntax and a generic provider adapter. It is not the eSignly API contract, and it does not assume that any specific provider endpoint, header or status name exists. Map it onto the documented behavior of your provider before use.
Schema with a database-enforced single active operation
CREATE TABLE supersession_op ( tenant_id uuid NOT NULL, operation_key text NOT NULL, document_ref text NOT NULL, base_revision int NOT NULL, target_revision int NOT NULL, payload_hash bytea NOT NULL, -- SHA-256 of canonical approved payload state text NOT NULL, version int NOT NULL DEFAULT 0, lease_owner text, lease_expires_at timestamptz, original_request_ref text NOT NULL, replacement_ref text, PRIMARY KEY (tenant_id, operation_key) ); -- At most one non-terminal operation per document, enforced by the database. CREATE UNIQUE INDEX one_active_op_per_document ON supersession_op (tenant_id, document_ref) WHERE state NOT IN ('completed', 'terminal_failed', 'blocked_signed');Intake: record the operation and schedule the work in one transaction
-- Run inside ONE transaction. Schedule work only if the insert returned a row. INSERT INTO supersession_op (tenant_id, operation_key, document_ref, base_revision, target_revision, payload_hash, state, original_request_ref) VALUES ($1, $2, $3, $4, $5, $6, 'accepted', $7) ON CONFLICT (tenant_id, operation_key) DO NOTHING RETURNING state; -- Row returned: new operation. In the same transaction: INSERT INTO outbox (tenant_id, operation_key, kind) VALUES ($1, $2, 'run_supersession'); -- No row returned: this key already exists. Read it and compare. SELECT payload_hash, state FROM supersession_op WHERE tenant_id = $1 AND operation_key = $2; -- hash differs -> reject with a conflict (same key, changed payload) -- hash matches -> return current state as a replay; schedule nothing new -- A unique-index violation on a DIFFERENT key means another operation is -- already active for this document: reject with a conflict as well.Because the operation row and the outbox row commit together, you cannot end up with an accepted correction that nobody will ever execute, or with work scheduled for an operation that was never recorded. This is the transactional outbox idea applied to this workflow. Acknowledge the user's request only after this transaction commits.
Execution: claim atomically, record intent, then act
// Illustrative. 'provider' is your own adapter, not the eSignly API contract. async function runSupersession(db, provider, tenantId, opKey, workerId) { // Atomic claim: one conditional UPDATE, not read-then-write. const op = await db.oneOrNone( `UPDATE supersession_op SET lease_owner = $3, lease_expires_at = now() + interval '60 seconds', version = version + 1 WHERE tenant_id = $1 AND operation_key = $2 AND state IN ('accepted','void_unknown','void_confirmed','replacement_unknown') AND (lease_owner IS NULL OR lease_expires_at < now()) RETURNING `, [tenantId, opKey, workerId]); if (!op) return; // another worker holds it, or it is terminal if (op.state === 'void_unknown') { // Reconcile FIRST. Never call void again before reading authoritative state. const s = await provider.getStatus(op.original_request_ref); if (s.status === 'voided') return cas(db, op, 'void_confirmed'); if (s.status === 'completed') return cas(db, op, 'blocked_signed'); if (s.status !== 'pending') return release(db, op); // still unknown; back off, escalate later // Still pending: fall through to one more void attempt. } if (op.state === 'accepted' || op.state === 'void_unknown') { // Commit intent BEFORE the external call so a crash is recoverable as unknown. op = await cas(db, op, 'void_in_flight'); try { await provider.voidRequest(op.original_request_ref); const s = await provider.getStatus(op.original_request_ref); // confirm, do not assume if (s.status === 'voided') return cas(db, op, 'void_confirmed'); if (s.status === 'completed') return cas(db, op, 'blocked_signed'); return cas(db, op, 'void_unknown'); } catch (err) { if (isDefinitiveRejection(err)) return cas(db, op, 'terminal_failed'); return cas(db, op, 'void_unknown'); // timeout or network error: unknown, not failed } } // void_confirmed -> replacement_in_flight -> completed follows the same pattern, // with its own reconcile step before any second create call. } // cas(): UPDATE ... SET state = $new, version = version + 1 // WHERE tenant_id = $1 AND operation_key = $2 AND version = $expected // and throw if zero rows were updated.What is a true compare-and-swap here, and what is not
Read-compare-then-write is not compare-and-swap. In the sketch above, the lease claim and every cas() transition are single conditional UPDATE statements against your own table, so they are genuine atomic claims. That protects you against two of your own workers.
It does not protect you against the signer, against a support agent using the provider dashboard, or against a second system that talks to the same provider account. For that, you need target-side conditional mutation: the provider must reject the void if the request is no longer in the state you expect. In plain HTTP, conditional requests are defined in RFC 9110, for example If-Match with an entity tag. That standard is scoped to HTTP resources whose servers implement validators. It does not make a vendor's void endpoint conditional unless the vendor documents it. Likewise, the IETF's work on an Idempotency-Key header is a draft convention. Check its current status and, more importantly, whether your provider honors any such header. Providers such as Stripe document their idempotency behavior in detail, which is a useful model for what to look for, not a statement about any eSignature vendor.
If your provider offers neither conditional mutation nor idempotency on void, disclose the limitation in your design: serialization happens only in your system, a small race window remains, and the post-call status read is what detects a lost race. That is why the sketch always confirms by reading status after the call. For broader idempotency patterns, see our guide to resilient eSignature APIs, idempotency and error handling.
Webhooks, Acknowledgment, Uncertain Input, Terminal Failure and Privacy Ownership
Key takeaway: A correction flow is only as safe as its handling of inbound events. Acknowledge delivery only after the event is durably recorded and its follow-up work is scheduled.
Outbound calls are half the problem. The other half is what your system does when the provider tells you something happened, especially something you did not expect, such as a completion event for a request you are in the middle of voiding.
Verified acknowledgment
Verify the webhook's signature over the raw request body before parsing it, using the method your provider documents. Then, in a single transaction, insert an inbox row keyed by (tenant_id, provider_event_id) or, if no event ID exists, by a hash of the verified body, record the state effect, and write the work item to your outbox. Return a success status only after that transaction commits. If the transaction fails, return an error so the provider retries. Acknowledging first and processing later is how events are lost. For deeper coverage, see our guides to webhook security and integrity and to dead-letter replay design.
Handling events on superseded requests
| Inbound event | Request mapping | Correct handling |
|---|---|---|
| Completed or signed event | Original request, operation in void_in_flight or void_unknown | Move to blocked_signed. Stop the replacement. Alert the named owner. |
| Completed or signed event | Original request, operation already completed (voided) | Treat as an anomaly: provider and your record disagree. Quarantine and investigate. Do not mark the contract executed. |
| Voided event | Original request, operation in void_unknown | Record as authoritative evidence. Move to void_confirmed after verifying. |
| Any event | Request reference your system does not know | Quarantine. Do not drop it and do not guess a mapping. |
| Duplicate delivery of a known event | Same event identity | Inbox uniqueness makes it a no-op. Acknowledge. |
Always key downstream effects such as "contract executed" on the specific request reference and revision, never on the customer or opportunity alone. That single rule would have prevented failure scenario 2 above.
Uncertain input and terminal failure
Define in advance what uncertain means for each input: an unmapped event, a status your code does not recognize, a timeout, a payload whose hash does not match. All of them follow the same rule: record, quarantine, reconcile or escalate. None of them is converted into success. Define terminal states too. blocked_signed and terminal_failed each need a named human owner, a queue they appear in, and an expectation for how fast they are reviewed. A terminal state with no owner is just a silent failure with a better name.
Privacy ownership
Signer email addresses, names and document references are personal or confidential data, and identifiers can themselves be sensitive. A few design rules follow:
- Do not embed signer emails or names in operation keys, log lines or metric labels. Use opaque references.
- Store the ledger's minimum: references, hashes, states and timestamps. Keep document bodies and signer details in the system that already owns them.
- A plain hash of a payload that contains low-entropy values, such as a short list of known emails, may be guessable. If the hash is stored long term or exposed, consider a keyed hash (HMAC) with a key managed per tenant.
- Name an owner for retention and deletion. Retention of superseded and voided request records may be required by law, contract or your own audit policy, and it may conflict with deletion requests. Resolve that with counsel, not in code comments.
Mandatory go/no-go controls
These are all-required checks. They are not a scored rubric, and passing them does not certify production readiness or legal sufficiency. It means the known failure classes in this article are handled.
| # | Control | Pass condition |
|---|---|---|
| 1 | Tenant-scoped operation identity | Key uniqueness includes tenant. A cross-tenant key collision test passes. |
| 2 | Immutable payload binding | Hash stored at approval. Same key with changed payload is rejected. |
| 3 | Atomic claim | Conditional update with lease. Two concurrent workers test shows exactly one proceeds. |
| 4 | One active operation per document | Enforced by a database constraint, not application logic. |
| 5 | Intent persisted before external call | Crash-after-call test lands in an unknown state, not in a lost state. |
| 6 | Reconcile before retry | No mutation is re-sent after a timeout until authoritative state is read. |
| 7 | Replacement gated on confirmed void | Replacement creation is impossible from any state except void_confirmed. |
| 8 | Atomic inbox, work scheduling and ack | Event record and scheduled work commit together. Ack follows commit. |
| 9 | Uncertain input quarantine | Unmapped events are retained and reviewable. Nothing is silently dropped. |
| 10 | Terminal failure ownership | blocked_signed and terminal_failed route to a named owner with a review expectation. |
| 11 | Privacy ownership documented | Data minimization, log redaction, retention and deletion owner are recorded. |
| 12 | Race tests exist | Void-versus-signer-completion and webhook-before-response orderings are tested. |
Risks, Constraints and Trade-Offs: Choosing How Much Machinery You Need
Key takeaway: The ledger adds real complexity. Use it where a wrong correction has a business or legal cost, and choose a lighter path where it does not.
What the design costs you
- More states, more code. Nine states and a reconciler are heavier than two API calls. You will maintain them.
- Slower corrections. Confirming the void before creating the replacement adds latency. That is deliberate. Speed is traded for the guarantee of at most one live request.
- Human work items.
blocked_signedandterminal_failedcreate review work. If you have no owner for them, do not automate. - Provider limits. Without provider-side conditional mutation or idempotency, a residual race remains. You detect it afterward rather than prevent it.
- Operational discipline. The ledger only helps if support staff stop making silent changes in the provider console, or if your webhook handling can absorb those changes as authoritative evidence.
Options compared
The ratings below are qualitative judgments for comparison, not benchmarks. Substitute your own volumes and risk tolerance.
| Option | Build effort | Risk of duplicate or wrong-version outcome | Correction speed | Scales with volume | Hidden failure mode |
|---|---|---|---|---|---|
| A. Manual correction in the provider dashboard | None | Moderate; depends on staff discipline | Slow | Poorly | Your database never learns what happened |
| B. Fire-and-forget void then create | Low | High on timeouts and races | Fast | Appears to scale, fails at the edges | Two live requests; late events attach to wrong version |
| C. Supersession Ledger (proposed) | Moderate to high | Low for known failure classes; residual race if provider lacks conditional mutation | Moderate | Well | Unowned terminal states pile up |
| D. Provider-native correction or amendment feature, if one exists | Low to moderate | Depends on provider's documented guarantees | Fast | Depends on provider | Behavior assumed rather than verified, with no visibility in your own state |
Recommendation by situation
- Low volume, low consequence, a human reviews each correction: Option A with a written procedure and a rule that every manual action is logged in your system the same day. It is acceptable if you accept the divergence risk.
- Moderate volume, customer-facing contracts, any regulated content: Option C, or Option D after you have verified in writing what the provider guarantees, including behavior on a timed-out or repeated call.
- Multi-tenant platform where one tenant's error must never affect another: Option C is effectively required, because tenant-scoped identity and per-document serialization are the point.
- Rarely, if ever: Option B. It is only defensible for throwaway internal acknowledgments where a duplicate costs nothing.
Whichever option you choose, keep one boundary absolute: corrections apply to requests that are still out for signature. A completed agreement is amended through a new, deliberate process, not by voiding.
A Lower-Risk Rollout: Test Matrix, Vendor Verification and Where eSignly Fits
Key takeaway: Verify what the provider actually guarantees, test the races on purpose, and roll out from observation to automation in stages.
A staged rollout
- Shadow mode. Record every correction in the ledger but let people execute it manually. Compare ledger state to provider state daily and learn how often they diverge.
- Automate case A only. Enable void-and-replace for requests where no signer has acted. Route everything else to a human work item.
- Add reconciler alerts. Alert on any operation stuck in an unknown state beyond an agreed time, and on any
blocked_signedoutcome. - Review quarterly. Look at your own correction rate, unknown rate, lost-race rate and the supersession race window. Adjust retry budgets and ownership from your data.
A test matrix worth running
| Test | Expected result |
|---|---|
| Void succeeds but response is lost | void_unknown, reconcile finds voided, move to void_confirmed. No second void blindly sent. |
| Signer completes between your decision and the void | blocked_signed. No replacement created. Owner alerted. |
| Replacement create succeeds but response is lost | replacement_unknown, resolved by correlation or escalated. No blind second create. |
| Same key, changed payload | Conflict rejected. No work scheduled. |
| Same key, same payload, repeated 50 times | One operation, one set of provider calls. |
| Two tenants use the same key string | Two independent operations. No cross-tenant replay. |
| Worker crashes after claim and before call | Lease expires, another worker resumes safely. |
| Webhook delivered twice, or before your API response returns | Single effect. Ordering does not corrupt state. |
Questions to put to any eSignature provider, in writing
- Is there a documented way to void or cancel a request that is still out for signature, and what happens if it is already completed?
- Does the API honor an idempotency key, a client reference or a conditional precondition on create and void? What is the key lifetime?
- Can you read authoritative request status reliably, and how quickly does it reflect a signer action?
- What events are sent to webhooks, how are they signed, what is the retry behavior, and is there an event identifier for deduplication?
- What does the audit trail record for a voided request, and how long is it retained and exportable?
- Which security and compliance statements are certifications held by the vendor, and which are features that support your own obligations?
Where eSignly fits, with scope stated
The public eSignly API page describes a REST API over HTTPS, using API keys, with a documented create-request call and webhooks for events such as viewed, signed and completed. The page shows an example completion payload containing a document identifier, signer email and signed timestamp, and states that events are signed. The integrations page describes webhook configuration through account settings. Those are vendor statements, and the code in this article is not derived from the eSignly API contract. Before building a supersession flow on eSignly, confirm in the current API reference how request status is read, whether and how a pending request is voided or cancelled, which webhook events and identifiers are available, and what idempotency or conditional behavior applies. If a capability is not documented, design as though it does not exist and disclose the limitation, as described above.
On security and compliance, the eSignly security page states that eSignly is audited and certified for SOC 2 Type II and ISO 27001 and describes support for HIPAA through Business Associate Agreements and a 21 CFR Part 11 module. These are the vendor's own statements. They describe controls the vendor operates and features that help support your compliance program. They do not make your correction workflow compliant on their own, and they do not settle which legal obligations apply to you. Request the current reports and agreements through your normal vendor review, and have counsel assess your specific use. For the evidence side of corrections, see our overview of what makes an eSignature audit trail defensible.
A short update note, then the evergreen rule
2026 update: At the time of this review, the public eSignly pages cited above describe the REST API, API-key authentication, create-request example and webhook events. Product details change, so treat the API reference as the source of truth. The durable principle does not depend on any vendor or year: a mutation across two systems is safe only when it has a durable identity, a bound payload, a single owner at a time, and a reconcile-first answer to every unknown.
Want to prototype a correction flow against a real signing API?
Start with the free plan, read the API reference, and test the void, status and webhook behavior against the control list above before you commit.
Try eSignly's API and see what your correction flow can rely on.
Free TrialConclusion: Five Actions to Take Before You Automate Corrections
A correction to a signature request that is already out is a distributed operation with a human on the other end. The goal is not to make it fast. The goal is that at most one request for an agreement revision is ever live, and that every ambiguous result is resolved by facts rather than hope.
- Classify your changes. Automate only the case where no signer has acted. Send mid-flight and post-completion changes to a human-reviewed path with counsel input.
- Write the invariants down. Tenant-scoped key, approved payload hash, one active operation per document, replacement gated on confirmed void. Enforce them in the database.
- Make unknown a first-class state. Reconcile against authoritative provider status before any retry of a consequential mutation, and treat a lost race as a legitimate outcome with an owner.
- Verify the provider in writing. Confirm void behavior, idempotency, conditional mutation, status reads and webhook signing. Disclose any residual race that remains.
- Run the race tests and name the owners. Do not enable automation until every control in the go/no-go table passes and someone owns the terminal states and the privacy decisions.
About eSignly and this review
eSignly is an online eSignature SaaS and API provider based in the USA, in business since 2014, offering web, API and on-premises options. Per its public security page, eSignly states SOC 2 Type II and ISO 27001 certification and offers HIPAA support through Business Associate Agreements and a 21 CFR Part 11 module. Those statements describe vendor controls and compliance-support features. Your own legal obligations depend on your use case and jurisdiction.
Reviewed by eSignly Expert Team.
Frequently Asked Questions
How do I void and resend a signature request without creating duplicates?
Record the correction as a durable operation before calling the provider. Include the tenant, a key derived from the document and base revision, and a hash of the approved replacement payload. Void the original, confirm its status from the provider, and only then create the replacement. If any call times out, read authoritative state before retrying.
- Reject the same key with a changed payload.
- Allow only one active operation per document.
- Never create the replacement while the original may still be live.
What should I do if the void call times out?
Treat it as unknown, not failed. A timeout means you could not confirm completion. Read the request's current status from the provider. If it is voided, move forward. If it is completed, stop and escalate, because the signer won the race. If it is still pending, a further void attempt may be reasonable, subject to what your provider documents about repeated void calls.
What happens if the signer completes the document before my void succeeds?
The original request stands. In the proposed design this is the blocked_signed state: the replacement is not created and a named owner decides what to do next. Whether an amendment or new agreement is appropriate is a business and legal decision, not an automatic retry.
Is an idempotency key enough to make corrections safe?
Not by itself. A key prevents duplicate execution of the same request only if the provider or your own system enforces it. You also need the key scoped to the tenant, bound to an immutable payload hash, and checked against changed-payload reuse. And the provider may not honor the key at all, so verify its documented behavior.
Can I use HTTP conditional requests like If-Match to protect a void?
Only if the provider's API supports them for that operation. RFC 9110 defines conditional request semantics for HTTP, but it does not oblige any particular vendor to implement validators on a void endpoint. If it is not documented, serialize in your own system and confirm by reading status after the call.
Should I edit a document that is already out for signature instead of voiding it?
Generally no. Editing in place risks the signer seeing or signing a different version than your records show. The safer pattern is to void the pending request and send a new one with the corrected document, recording the reason. For completed agreements, use a separate amendment process reviewed by counsel.
Does eSignly support voiding a pending signature request through the API?
The public eSignly pages describe a REST API, API-key authentication, a create-request call and webhooks for signing events. They do not, in the material reviewed for this article, specify a void endpoint or idempotency behavior. Check the current eSignly API reference for the exact operations and guarantees before you build, and design around any capability that is not documented.
Who owns personal data in a correction ledger?
Your organization decides, and should record the decision. Keep the ledger to opaque references, hashes, states and timestamps, keep signer details in the system that already owns them, avoid placing emails in keys or logs, and have counsel confirm retention and deletion rules for superseded and voided records.
Corrections should not be a coin flip.
If your team is designing void, resend and reconcile flows, test them against a real signing API with webhooks, audit trails and free-plan access.
Explore eSignly's API plans and build your first reliable signing workflow.
Free TrialResource Esignature Api Guide
This article is most relevant for CTOs and developers who need to roll out a practical signing workflow. Use the related eSignly path to compare plans, API options, compliance fit, and implementation next steps.
Reviewed for electronic signature decision makers
This guide is reviewed for clarity, legal and operational relevance, service alignment, and practical conversion path before being connected to an eSignly plan or API workflow.
For regulated, high-volume, or customer-facing workflows, validate legal duties, plan assumptions, and integration requirements with your internal stakeholders before rollout.

