Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/TurboSign/API-Signatures.md
Original file line number Diff line number Diff line change
Expand Up @@ -2179,7 +2179,7 @@ formData.Add(new StringContent(recipients), "recipients");
| `402` | `OtpNotEntitled`, `SmsOtpLimitExceeded` | The plan does not include this verification, or the SMS allowance is used up. |
| `403` | `ExternalIdvNotAllowed`, `IdentityOverrideNotAllowed`, `OtpOverrideNotAllowed`, `EmbeddedSigningNotEnabled`, `SmsOtpNotEnabled` | The organization has not turned on the feature or mode the request uses, or has locked the passcode method. |
| `404` | `DocumentNotFound`, `RecipientNotFound` | The document or recipient does not exist, was deleted, or belongs to another organization. On `update-with-recipients`, a document ID that does not exist returns `404` with only `{ "error": "Document not found" }`. |
| `409` | `DocumentNotSignable`, `RecipientAlreadySigned`, `RecipientNotInTurn`, `NotSignersTurn`, `SmsProviderNotConfigured`, `RecipientRequiresSingleUseUrl` | The document or recipient is not in a state that allows the request, or the organization has no SMS provider saved. |
| `409` | `DocumentNotSignable`, `RecipientAlreadySigned`, `RecipientNotInTurn`, `SmsProviderNotConfigured`, `RecipientRequiresSingleUseUrl` | The document or recipient is not in a state that allows the request, or the organization has no SMS provider saved. |
| `410` | `SigningUrlNotRedeemable` | A single-use signing URL was already used or has expired. |

None of these signature errors return a `5xx` status. A `500` from these endpoints means an unexpected server problem, not a problem with your request.
Expand Down
22 changes: 8 additions & 14 deletions docs/TurboSign/embedded-signing/build/sequential-signers.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Return the `documentId` and the list (with each `recipientId`) to your page.

## Step 3: Mint the next signer's URL when it is their turn

When a signer finishes, the signing page sends `turbosign:completed` right away, but TurboSign advances the turn a moment later. A mint fired immediately can get HTTP `409` with `RecipientNotInTurn` or `NotSignersTurn`. Retry those two codes a few times with a short delay; treat every other error as real.
When a signer finishes, the signing page sends `turbosign:completed` right away, but TurboSign advances the turn a moment later. A mint fired immediately can get HTTP `409` with `RecipientNotInTurn`. Retry that code a few times with a short delay; treat every other error as real.

Both server calls, Step 2 and Step 3, in each SDK language:

Expand Down Expand Up @@ -87,15 +87,13 @@ export async function startKiosk(signers: Array<{ name: string; email: string }>
}

// Step 3: POST /api/kiosk/next
const TURN_RACE = new Set(["RecipientNotInTurn", "NotSignersTurn"]);

export async function mintNext(documentId: string, recipientId: string): Promise<string> {
for (let attempt = 0; ; attempt++) {
try {
const { url } = await TurboSign.createSigningUrl(documentId, { recipientId });
return url;
} catch (err: any) {
if (!TURN_RACE.has(err?.code) || attempt >= 5) throw err;
if (err?.code !== "RecipientNotInTurn" || attempt >= 5) throw err;
await new Promise((r) => setTimeout(r, 1500));
}
}
Expand Down Expand Up @@ -130,15 +128,13 @@ async def start_kiosk(signers: list[dict]) -> dict:
) # {"documentId": ..., "recipients": [...]}, first one "ready"

# Step 3: POST /api/kiosk/next
TURN_RACE = {"RecipientNotInTurn", "NotSignersTurn"}

async def mint_next(document_id: str, recipient_id: str) -> str:
for attempt in range(6):
try:
link = await TurboSign.create_signing_url(document_id, recipient_id=recipient_id)
return link["url"]
except ConflictError as err:
if err.code not in TURN_RACE or attempt == 5:
if err.code != "RecipientNotInTurn" or attempt == 5:
raise
await asyncio.sleep(1.5)
```
Expand Down Expand Up @@ -185,7 +181,7 @@ function mintNext(string $documentId, string $recipientId): string
$link = TurboSign::createSigningUrl($documentId, new CreateSigningUrlRequest(recipientId: $recipientId));
return $link->url;
} catch (ConflictException $e) {
$race = in_array($e->errorCode, ['RecipientNotInTurn', 'NotSignersTurn'], true);
$race = $e->errorCode === 'RecipientNotInTurn';
if (!$race || $attempt >= 5) {
throw $e;
}
Expand Down Expand Up @@ -230,7 +226,7 @@ func MintNext(ctx context.Context, client *turbodocx.Client, documentID, recipie
return link.URL, nil
}
var conflict *turbodocx.ConflictError
race := errors.As(err, &conflict) && (conflict.Code == "RecipientNotInTurn" || conflict.Code == "NotSignersTurn")
race := errors.As(err, &conflict) && conflict.Code == "RecipientNotInTurn"
if !race || attempt >= 5 {
return "", err
}
Expand Down Expand Up @@ -274,7 +270,7 @@ public String mintNext(String documentId, String recipientId) throws Exception {
return client.turboSign().createSigningUrl(documentId,
new CreateSigningUrlRequest.Builder().recipientId(recipientId).build()).getUrl();
} catch (TurboDocxException.ConflictException e) {
boolean race = "RecipientNotInTurn".equals(e.getCode()) || "NotSignersTurn".equals(e.getCode());
boolean race = "RecipientNotInTurn".equals(e.getCode());
if (!race || attempt >= 5) throw e;
Thread.sleep(1500);
}
Expand Down Expand Up @@ -305,14 +301,12 @@ def start_kiosk(signers)
end

# Step 3: POST /api/kiosk/next
TURN_RACE = %w[RecipientNotInTurn NotSignersTurn].freeze

def mint_next(document_id, recipient_id)
attempts = 0
begin
TurboDocxSdk::TurboSign.create_signing_url(document_id, recipient_id: recipient_id)["url"]
rescue TurboDocxSdk::ConflictError => e
raise unless TURN_RACE.include?(e.code) && (attempts += 1) <= 5
raise unless e.code == "RecipientNotInTurn" && (attempts += 1) <= 5

sleep 1.5
retry
Expand Down Expand Up @@ -412,7 +406,7 @@ For the **whole document**, don't rely on the last browser event. Check the docu

| Symptom | Cause | Fix |
|---|---|---|
| HTTP `409` `RecipientNotInTurn` or `NotSignersTurn` right after a signer finishes | TurboSign has not advanced the turn yet. | Retry for a few seconds, as in Step 3. |
| HTTP `409` `RecipientNotInTurn` right after a signer finishes | TurboSign has not advanced the turn yet. | Retry for a few seconds, as in Step 3. |
| HTTP `409` `RecipientNotInTurn` that never clears | An earlier signer has not actually signed. | Frame the earlier signer again. |
| HTTP `409` `RecipientAlreadySigned` | That signer already signed. | Skip to the next one. |
| The second signer's panel is blank | Same as any blank frame: your origin is not allowed. | Add your origin under **Allowed embedding domains**. |
Expand Down
2 changes: 1 addition & 1 deletion docs/TurboSign/embedded-signing/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ The [API reference](./reference.md) has every request and response field.
| 403 | none (a plain `403 Forbidden`) | The API key belongs to a **User**. Use an **Administrator** or **Contributor** key. |
| 403 | `EmbeddedSigningNotEnabled` | Ask an admin to turn on **Enable identity verification**. |
| 403 | `OtpOverrideNotAllowed` | Your organization locked the passcode method. Omit the channel, or ask an admin to let senders change it. |
| 409 | `RecipientNotInTurn`, `NotSignersTurn` | An earlier signer has not finished. Mint this signer's URL when it is their turn. |
| 409 | `RecipientNotInTurn` | An earlier signer has not finished. Mint this signer's URL when it is their turn. |
| 410 | `SigningUrlNotRedeemable` | A single-use URL was used or expired. Request a new one. |

Every error code is in the [API reference](./reference.md#errors).
Expand Down
4 changes: 2 additions & 2 deletions docs/TurboSign/embedded-signing/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ url = link["url"]
| 403 | none (a plain `403 Forbidden`) | The API key belongs to a **User**. |
| 403 | `EmbeddedSigningNotEnabled`, `ExternalIdvNotAllowed`, `IdentityOverrideNotAllowed` | The organization has not turned on what the recipient uses. |
| 404 | `RecipientNotFound` | No recipient matches the selector. |
| 409 | `RecipientNotInTurn`, `NotSignersTurn`, `RecipientAlreadySigned`, `DocumentNotSignable` | Not this signer's turn, already signed, or the document can't be signed. |
| 409 | `RecipientNotInTurn`, `RecipientAlreadySigned`, `DocumentNotSignable` | Not this signer's turn, already signed, or the document can't be signed. |
| 410 | `SigningUrlNotRedeemable` | A single-use URL was opened already or expired. Request a new one. |

:::caution Deprecated: GET signing-link
Expand Down Expand Up @@ -532,7 +532,7 @@ Identity and passcode errors from sending a document and from `createSigningUrl`
| 404 | `RecipientNotFound` | No recipient on the document matches the selector. |
| 409 | `SmsProviderNotConfigured` | The recipient resolves to SMS but the organization has no SMS provider saved. |
| 409 | `RecipientRequiresSingleUseUrl` | The deprecated `signing-link` endpoint was called for an `external_idv` or `override` recipient. Use `createSigningUrl`. |
| 409 | `DocumentNotSignable`, `RecipientAlreadySigned`, `RecipientNotInTurn`, `NotSignersTurn` | The document or recipient is not in a signable state. |
| 409 | `DocumentNotSignable`, `RecipientAlreadySigned`, `RecipientNotInTurn` | The document or recipient is not in a signable state. |
| 410 | `SigningUrlNotRedeemable` | A single-use signing URL was already used or has expired. Request a new one. |
| 423 | `locked` | The signer entered 20 wrong codes. See [Passcode attempts and lockout](#passcode-attempts-and-lockout). |

Expand Down
Loading