{"record":{"id":"4e32245013fd1585","repo":"Stirling-Tools/Stirling-PDF","slug":"failed-to-submit-signature","errorCode":null,"errorMessage":"Failed to submit signature","messagePattern":"Failed to submit signature","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/proprietary/hooks/workflow/useParticipantSession.ts","lineNumber":72,"sourceCode":"  const submitSignature = useCallback(\n    async (request: SignatureSubmissionRequest) => {\n      setLoading(true);\n      setError(null);\n      try {\n        const updatedParticipant =\n          await workflowService.submitSignature(request);\n        setParticipant(updatedParticipant);\n        // Reload session to get updated status\n        if (request.participantToken) {\n          await loadSession(request.participantToken);\n        }\n      } catch (err: unknown) {\n        const errorMsg = isAxiosError(err)\n          ? err.response?.data?.message || err.message\n          : (err instanceof Error ? err.message : undefined) ||\n            \"Failed to submit signature\";\n        setError(errorMsg);\n        throw new Error(errorMsg, { cause: err });\n      } finally {\n        setLoading(false);\n      }\n    },\n    [loadSession],\n  );\n\n  const decline = useCallback(\n    async (token: string, reason?: string) => {\n      setLoading(true);\n      setError(null);\n      try {\n        const updatedParticipant = await workflowService.declineParticipation(\n          token,\n          reason,\n        );\n        setParticipant(updatedParticipant);\n        // Reload session","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/proprietary/hooks/workflow/useParticipantSession.ts#L54-L90","documentation":"Thrown by submitSignature() in useParticipantSession after workflowService.submitSignature (or the subsequent loadSession reload) rejects. The hook first tries to extract a usable message: axios errors use response.data.message, other Errors use err.message, and only if neither exists does it fall back to 'Failed to submit signature'. The thrown Error carries the original as {cause} and the same string is set into the error state for UI.","triggerScenarios":"Backend rejects the signature (invalid signature image, missing required fields, participant token expired, session already completed/declined); network failure during the POST; participantToken missing so the session reload is skipped but the submit itself failed; concurrent double-submit where the second call hits a now-invalid state.","commonSituations":"Participant let their email token link expire; the workflow session was cancelled by an admin mid-sign; backend validation rejects an empty signature pad; flaky network on mobile; the request.participantToken is undefined so signature submits but state desyncs.","solutions":["Check the axios response status/message shown in error state — it usually carries the real backend reason.","Ensure SignatureSubmissionRequest has a valid participantToken and all required signature fields before submitting.","Handle the thrown error at the call site (it re-throws after setting state) and surface the backend message to the user.","If the token expired, re-issue/re-fetch the participant link rather than retrying the same request."],"exampleFix":"// before\ntry { await submitSignature(req); } catch { /* swallow */ }\n\n// after\ntry {\n  await submitSignature(req);\n} catch (e) {\n  showToast(e instanceof Error ? e.message : 'Signature failed');\n  if (tokenExpired(e)) refreshParticipantLink();\n}","handlingStrategy":"try-catch","validationCode":"const req = request;\nif (!req.participantToken) {\n  setError('Participant token is required');\n  return;\n}\n// ensure signature fields are populated before calling submitSignature","typeGuard":"export function isCompleteSignatureRequest(r: SignatureSubmissionRequest): boolean {\n  return Boolean(r.participantToken);\n}","tryCatchPattern":"try {\n  await submitSignature(request);\n} catch (e) {\n  const msg = e instanceof Error ? e.message : 'Signature failed';\n  showToast(msg);\n  if (/token|expired|unauthorized/i.test(msg)) refreshParticipantLink();\n}","preventionTips":["Disable the submit button while loading and after first click to prevent double-submit races.","Validate the participant token hasn't expired before rendering the sign UI.","Always surface error state (already set by the hook) to the user rather than swallowing the re-thrown error."],"tags":["workflow","signature","axios","participant","proprietary","typescript"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}