Status Updates

Push signature lifecycle events from your middleware back to Ironclad so the workflow can move forward.

Your middleware notifies Ironclad of signature lifecycle changes by calling the Ironclad public API. This is the inbound half of the integration. See BYOSP Overview for the full picture.

PATCH /public/api/v1/signature-requests/{id}

Update Custom Signature Request Status (BYOSP)

  • {id} is the signatureRequestId Ironclad sent you at initiation.
  • Returns 204 No Content on success.
  • Idempotent at the signature request level. Once a signature request has reached completed or voided, further pushes are no-ops.

Authentication and Scope

  • Authorization: Authorization: Bearer <token>, using an Ironclad public API OAuth 2.0 bearer token. No CSRF token is needed; the public API is header-authenticated.
  • Required OAuth scope: public.signatureRequests.updateRequest.
📌

The Token Must Belong to the Provider's Own Client App

Ironclad looks up the custom signature provider that the signature request was sent through and compares its current client app to the clientId on your bearer token.

A token minted for a different provider in the same company returns 403, as does any token without a clientId (i.e. a legacy company-scoped bearer token). Use the client app created on the provider in Ironclad Setup.

Two further access rules apply:

  • A signature request that was not pinned to a specific signature provider cannot be bound to a caller and returns 403 on every push.
  • The token's company must own the referenced signature request. A request for one owned by a different company returns 404 rather than 403, to avoid leaking existence.
📌

User Context Is Required

Your client app authenticates with the client credentials grant, so every push must also carry a user header (either x-as-user-id or x-as-user-email) with a valid user ID or email. The request's scope and context are evaluated with respect to that user.

See the client credentials user requirement in Authenticate a Request.

Use a Dedicated Integration User

We recommend creating a dedicated integration user in Ironclad for your middleware to act as, rather than sending a particular employee's identity on the x-as-user-email header.

  • Stability. The integration keeps working when people change roles or leave the company. Pointing at an individual means their deactivation silently breaks every status push.
  • Clear attribution. Signature activity that Ironclad records for these updates is traceable to the integration rather than to a person who did not perform the action.
  • Scoped permissions. The user's permissions bound what the request can do, so a purpose-built user can be granted only what the middleware needs.

Use that user's email or ID consistently on every push.

Request Body

Unknown or extra keys are rejected.

⚠️

Send the Full Signatures Array on Every Push

Each push replaces the signer list on the signature request. Include an entry for every signer, including those whose state has not changed.

This is not a partial update. Do not send only the signer who just acted.

FieldTypeRequiredDescription
statuspending | completed | voidedYesThe status of the signature request as a whole. Ironclad trusts this over any inference from signer states.
signatures[]array of objectsYesEvery signer on the signature request, including those whose state has not changed.
signatures[].roleNamestringYesMust match a roleName from the original initiation request, and must not repeat within a single push.
signatures[].statusawaiting_signature | signed | declinedYesThis signer's current state.
signatures[].signer.namestringYesThe signer's current full name, as your provider has it.
signatures[].signer.emailstring (email)YesThe signer's current email address, as your provider has it.
signatures[].declineReasonstringNoPersisted only when this signer's status is declined.
signatures[].firstViewedAtstring (ISO-8601)NoWhen the signer first viewed the request. Once Ironclad has a value for a signer it is never overwritten, so it is safe to send the same value on every push.
📌

Reassignment and Delegation

Always send the current signer.name and signer.email from your provider. Sending the current values is how a reassignment or delegation at your provider reaches Ironclad. There is no separate reassignment call.

email is what drives the change. A new name on its own does not update the signer. Ironclad applies a changed name only when it arrives alongside a changed email, or when the signer signs.

Signer title and routing order are owned by Ironclad. They are retained from the values established at initiation and are neither sent nor accepted here.

A push after one of two signers has signed. Both signers are present:

{
  "status": "pending",
  "signatures": [
    {
      "roleName": "Buyer",
      "status": "signed",
      "signer": {
        "name": "Kim Lam",
        "email": "[email protected]"
      },
      "firstViewedAt": "2026-01-15T09:30:00Z"
    },
    {
      "roleName": "Seller",
      "status": "awaiting_signature",
      "signer": {
        "name": "Jimothy Kim",
        "email": "[email protected]"
      }
    }
  ]
}

A push where the second signer declined:

{
  "status": "voided",
  "signatures": [
    {
      "roleName": "Buyer",
      "status": "signed",
      "signer": {
        "name": "Kim Lam",
        "email": "[email protected]"
      },
      "firstViewedAt": "2026-01-15T09:30:00Z"
    },
    {
      "roleName": "Seller",
      "status": "declined",
      "signer": {
        "name": "Jimothy Kim",
        "email": "[email protected]"
      },
      "firstViewedAt": "2026-01-15T11:02:00Z",
      "declineReason": "Terms need revision before signing."
    }
  ]
}

Rules to be aware of:

  • Every signatures[].roleName must be one of the role names from the original initiation request, or the call returns 400.
  • A roleName must not repeat within a single push, or the call returns 400.
  • Once Ironclad has a firstViewedAt value for a signer, later pushes do not overwrite it. It is safe to send the same value on every push.
  • declineReason is stored only when that signer's status is declined, and is ignored otherwise.

Completion Must Be Explicit

Ironclad does not infer that a signature request is finished from the state of its signers. The request-level status is the only thing that completes it.

A push where every signer is signed but status is still pending leaves the request open. The workflow stays on the Sign step, the signed document is not collected, and nothing downstream fires, even though signing is done from your provider's perspective.

📌

Send completed With the Final Signature

Set status to completed on the same push that reports the last signer as signed, rather than sending the signature first and following up with a second push to complete the request.

A single push leaves no window in which every signer has signed but the workflow has not advanced. It also removes the failure mode where the follow-up push never arrives (i.e. after a crash, a network fault, or a dropped retry), leaving the request stranded open with no signer left to act.

The same applies to termination. When your provider ends a request without full signature, report the terminal request status in the same push as the signer state, sending status: "voided" alongside the signer's status: "declined".

A declined signer closes the request on its own, so the workflow leaves the Sign step either way. What the request-level voided adds is marking the request itself terminal, which is what makes later pushes no-ops. Sending both together is safe, and the decline is still attributed to the signer who declined.

Ironclad does not distinguish one provider-side termination from another. A voided request with no declined signer is recorded without a specific cause, so an expiration and a cancellation on your provider's side are indistinguishable to Ironclad.

Once completed or voided is recorded, further pushes are no-ops, so there is no cost to including the final state as early as you can determine it.

Response Codes

CodeMeaning
204Success, including an idempotent no-op on a signature request that is already completed or voided.
400Malformed body, unknown key, a roleName not present in the original request, a roleName that repeats within the push, or a malformed x-as-user-id value.
401Missing or invalid bearer token.
403Token lacks the public.signatureRequests.updateRequest scope, the caller is not the signature provider's current client app, or the acting user on x-as-user-id / x-as-user-email is missing, unrecognized, or not a member of the token's company (see Use a Dedicated Integration User).
404Unknown signatureRequestId, or the signature request belongs to a different company.

Example

curl -X PATCH \
  "https://ironcladapp.com/public/api/v1/signature-requests/3f0c8b12-5e44-4c1a-9d77-2b91f0a4de21" \
  -H "Authorization: Bearer $IRONCLAD_TOKEN" \
  -H "x-as-user-email: $IRONCLAD_USER_EMAIL" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "completed",
    "signatures": [
      {
        "roleName": "Buyer",
        "status": "signed",
        "signer": { "name": "Kim Lam", "email": "[email protected]" }
      },
      {
        "roleName": "Seller",
        "status": "signed",
        "signer": { "name": "Jimothy Kim", "email": "[email protected]" }
      }
    ]
  }'

Did this page help you?