---
updatedAt: 2026-08-26T22:59:07.000Z
agentTools:
  projectIndex: https://developer.ironcladapp.com/llms.txt
---

# 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](https://developer.ironcladapp.com/docs/byosp-overview) for the full picture.

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

[Update Custom Signature Request Status (BYOSP)](https://developer.ironcladapp.com/reference/update-custom-signature-request-status)

* `{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`.

<Callout icon="📌" theme="default">
  ### 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](https://developer.ironcladapp.com/docs/byosp-ironclad-setup).
</Callout>

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.

<Callout icon="📌" theme="default">
  ### 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](https://developer.ironcladapp.com/reference/authenticate-a-request).
</Callout>

## 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.

<Callout icon="⚠️" theme="warn">
  ### 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.
</Callout>

| Field                        | Type                                           | Required | Description                                                                                                                                                   |
| ---------------------------- | ---------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`                     | `pending` \| `completed` \| `voided`           | Yes      | The status of the signature request as a whole. Ironclad trusts this over any inference from signer states.                                                   |
| `signatures[]`               | array of objects                               | Yes      | Every signer on the signature request, including those whose state has not changed.                                                                           |
| `signatures[].roleName`      | string                                         | Yes      | Must match a `roleName` from the original initiation request, and must not repeat within a single push.                                                       |
| `signatures[].status`        | `awaiting_signature` \| `signed` \| `declined` | Yes      | This signer's current state.                                                                                                                                  |
| `signatures[].signer.name`   | string                                         | Yes      | The signer's current full name, as your provider has it.                                                                                                      |
| `signatures[].signer.email`  | string (email)                                 | Yes      | The signer's current email address, as your provider has it.                                                                                                  |
| `signatures[].declineReason` | string                                         | No       | Persisted only when this signer's `status` is `declined`.                                                                                                     |
| `signatures[].firstViewedAt` | string (ISO-8601)                              | No       | When 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. |

<Callout icon="📌" theme="default">
  ### 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.
</Callout>

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:

```json
{
  "status": "pending",
  "signatures": [
    {
      "roleName": "Buyer",
      "status": "signed",
      "signer": {
        "name": "Kim Lam",
        "email": "kimlam@example.com"
      },
      "firstViewedAt": "2026-01-15T09:30:00Z"
    },
    {
      "roleName": "Seller",
      "status": "awaiting_signature",
      "signer": {
        "name": "Jimothy Kim",
        "email": "jimothykim@example.com"
      }
    }
  ]
}
```

A push where the second signer declined:

```json
{
  "status": "voided",
  "signatures": [
    {
      "roleName": "Buyer",
      "status": "signed",
      "signer": {
        "name": "Kim Lam",
        "email": "kimlam@example.com"
      },
      "firstViewedAt": "2026-01-15T09:30:00Z"
    },
    {
      "roleName": "Seller",
      "status": "declined",
      "signer": {
        "name": "Jimothy Kim",
        "email": "jimothykim@example.com"
      },
      "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.

<Callout icon="📌" theme="default">
  ### 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.
</Callout>

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

| Code  | Meaning                                                                                                                                                                                                                                                                                                                               |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `204` | Success, including an idempotent no-op on a signature request that is already `completed` or `voided`.                                                                                                                                                                                                                                |
| `400` | Malformed 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.                                                                                                                                                                        |
| `401` | Missing or invalid bearer token.                                                                                                                                                                                                                                                                                                      |
| `403` | Token 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](#use-a-dedicated-integration-user)). |
| `404` | Unknown `signatureRequestId`, or the signature request belongs to a different company.                                                                                                                                                                                                                                                |

# Example

```bash
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": "kimlam@example.com" }
      },
      {
        "roleName": "Seller",
        "status": "signed",
        "signer": { "name": "Jimothy Kim", "email": "jimothykim@example.com" }
      }
    ]
  }'
```