Middleware Endpoints
The five HTTPS endpoints your middleware must implement for Ironclad to initiate, track, and complete a signature request.
These endpoints are required in order to use the BYOSP feature. All paths are relative to the endpointUrl you configured in Ironclad Setup.
{id} is the Ironclad signature request ID, a UUID that Ironclad generates at initiation, sends as signatureRequestId in the initiate body, and passes as a path segment on every call after it.
Every Request Is SignedEvery outbound request carries the
X-Ironclad-Request-Signatureheader. We recommend verifying it before acting on the request. See Request Signing.
Endpoint Summary
| Operation | Method and path | Request body | Expected response |
|---|---|---|---|
| Initiate | POST {endpointUrl} | multipart/form-data | 200 JSON { status: "pending", signatures[] } |
| Status | GET {endpointUrl}/{id}/status | none | 200 JSON { status, signatures[] } |
| Cancel | POST {endpointUrl}/{id}/cancel | {} | 2xx |
| Remind | POST {endpointUrl}/{id}/remind | { "roleName": "..." } | 2xx |
| Document | GET {endpointUrl}/{id}/document | none | 200 application/pdf (≤ 50 MB) |
Non-2xx responses and timeouts are surfaced to Ironclad as middleware errors. Status, cancel, and remind must respond within 5 seconds; initiate and document fetch within 15 seconds.
Initiate
POST {endpointUrl}
Sent as multipart/form-data with these parts:
| Part | Content type | Description |
|---|---|---|
body | application/json | The JSON payload described below. This is the part covered by the request signature. |
document_0, document_1, … | application/pdf | One part per PDF file, indexed in order. |
Single Document TodayIronclad currently sends a single document per signature request. The indexed multipart parts exist for forward compatibility.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
signatureRequestId | string (UUID) | Yes | The signature request ID. Send this as the {id} path parameter on inbound pushes. |
routingType | sequential | parallel | Yes | Signing order for the request. |
signers[] | array of objects | Yes | The signers on the request. |
signers[].roleName | string | Yes | Unique within the request. The only signer identifier on the wire. |
signers[].name | string | Yes | The signer's full name. |
signers[].email | string (email) | Yes | The signer's email address. |
signers[].title | string | Yes | The signer's title. May be an empty string. |
signers[].routingOrder | integer | Conditional | The order in which this signer signs. Present when routingType is sequential. |
cc[] | array of objects | Yes | Observers. Always present; [] when there are none. |
cc[].email | string (email) | Yes | The observer's email address. |
cc[].name | string | No | The observer's name. |
fields[] | array of objects | Yes | Tag placements. May be empty when signer tags are handled externally. |
fields[].type | enum | Yes | One of signature, date, text, name, email, title, initials, checkbox, number, editableEmail, editableDate, stamp. |
fields[].roleName | string | Yes | The signer this tag belongs to. |
fields[].x, fields[].y | number | Yes | Tag position on the page. |
fields[].page | integer | Yes | Page number, 1-indexed. |
fields[].width, fields[].height | number | Yes | Tag dimensions. Always greater than zero. |
fields[].required | boolean | Yes | Whether the signer must complete this tag. |
fields[].pageWidth, fields[].pageHeight | number | Yes | Dimensions of the page the tag sits on. Always greater than zero. |
fields[].tagOrigin | embedded | placed | No | How the tag originated. |
fields[].tagId | string | No | Identifier for the tag: a seven-character lowercase alphanumeric ID over 0-9 and a-z, such as 2gbqtjl. Corresponds to the [tagId] text tag in the source document. Not present on every tag. |
documentHashes[] | array of strings | Yes | SHA-256 hex digest of each document_N part, in upload order. Expect a single entry for now, since Ironclad currently sends one document per signature request. |
expiresAt | string (ISO-8601) | No | Expected expiration time in UTC. Present only when a signing deadline is set. |
fromName | string | No | The sender name to present to signers. |
emailSettings.subject | string | Yes | Subject line for the signature request email. |
emailSettings.body | string | Yes | Body text for the signature request email. |
Example:
{
"signatureRequestId": "3f0c8b12-5e44-4c1a-9d77-2b91f0a4de21",
"routingType": "sequential",
"signers": [
{
"roleName": "Buyer",
"name": "Kim Lam",
"email": "[email protected]",
"title": "",
"routingOrder": 0
},
{
"roleName": "Seller",
"name": "Jimothy Kim",
"email": "[email protected]",
"title": "General Counsel",
"routingOrder": 1
}
],
"cc": [],
"fields": [
{
"type": "signature",
"roleName": "Buyer",
"x": 72,
"y": 640,
"page": 1,
"width": 150,
"height": 30,
"required": true,
"pageWidth": 612,
"pageHeight": 792,
"tagOrigin": "embedded",
"tagId": "2gbqtjl"
},
{
"type": "signature",
"roleName": "Seller",
"x": 320,
"y": 640,
"page": 1,
"width": 150,
"height": 30,
"required": true,
"pageWidth": 612,
"pageHeight": 792,
"tagOrigin": "placed"
}
],
"documentHashes": ["e3b0c44298fc1c14..."],
"expiresAt": "2026-02-01T00:00:00.000Z",
"fromName": "Ironclad",
"emailSettings": {
"subject": "Please sign: Mutual NDA",
"body": "Your signature is requested on the attached agreement."
}
}
Signer Identity IsroleNameIronclad's internal signer ID is never sent on the wire.
roleNameis unique within a request, is the key you must echo back on every inbound status push, and is what you receive onremind.
documentHashes is how document integrity is bound to the request signature: the signature covers the JSON body string, the body contains documentHashes, and each hash is the SHA-256 of the corresponding document_N part. See Document Integrity.
Expected Response
| Field | Type | Required | Description |
|---|---|---|---|
status | pending | Yes | Must be exactly pending on initiate. |
signatures[] | array of objects | Yes | The initial state of every signer on the signature request. Signers omitted here are removed. |
signatures[].roleName | string | Yes | Must be unique across the array and match a roleName from the request. |
signatures[].signer.name | string | Yes | The signer's full name. |
signatures[].signer.email | string (email) | Yes | The signer's email address. |
signatures[].status | awaiting_signature | signed | declined | Yes | This signer's current state. |
signatures[].declineReason | string | No | Only when 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. |
Example:
{
"status": "pending",
"signatures": [
{
"roleName": "Buyer",
"signer": {
"name": "Kim Lam",
"email": "[email protected]"
},
"status": "awaiting_signature"
},
{
"roleName": "Seller",
"signer": {
"name": "Jimothy Kim",
"email": "[email protected]"
},
"status": "awaiting_signature"
}
]
}Ironclad validates this response. Unknown keys are ignored rather than rejected, so a misspelled field is silently dropped, while a missing or malformed required field fails the call.
Return the Full Signatures ArrayInclude an entry for every signer on the signature request, including signers whose state has not changed. Any signer omitted from the array is removed from the signature request.
Removal is permanent. A signer dropped from the array cannot be re-added to that signature request. Their
roleNamestops being valid, so a later status push that includes them again returns400rather than restoring them.This applies to the initiate response, the status response, and every inbound status push. It is never a partial update.
signer.title and signer.routingOrder are sent to you at initiation but cannot be updated from the middleware, so they are not expected back. Omit them from signer here and on status pushes.
Status
GET {endpointUrl}/{id}/status
Returns the same response shape as Initiate, except that status may be any of the following:
| Value | Meaning |
|---|---|
pending | The signature request is open and awaiting signer action. |
completed | All required signatures have been collected. |
voided | The signature request was cancelled or otherwise terminated. |
Ironclad trusts the request-level status field over any inference from individual signer states. If every signer has signed but you return pending, the signature request stays open.
As with initiate, return the full signatures array on every response. A signer omitted from the array is removed from the signature request.
Cancel
POST {endpointUrl}/{id}/cancel
Terminate the signature request with your provider.
Request body: {}
Expected response: any 2xx. The response body is ignored.
Stop Pushing After a Successful CancelOnce your middleware returns
2xx, Ironclad discards its record of the signature request. The ID stops existing, so a later status push for it returns404rather than being absorbed as a no-op.This differs from a request you reported as
completedorvoidedyourself, where the record is retained and further pushes are accepted as no-ops. Treat a successful cancel as the end of that ID's life and drop any queued or in-flight pushes for it.
Remind
POST {endpointUrl}/{id}/remind
Remind an outstanding signer.
| Field | Type | Required | Description |
|---|---|---|---|
roleName | string | No | The signer to remind. |
Ironclad only reminds signers who have not signed on an open signature request, and today always targets a single signer, so roleName is always present in practice. It is documented as optional for forward compatibility. If you ever receive it omitted, remind all outstanding signers.
Expected response: any 2xx. The response body is ignored.
Document
GET {endpointUrl}/{id}/document
Return the signed PDF with Content-Type: application/pdf. Ironclad enforces the following constraints on the response:
- Content type must start with
application/pdf, or the response is rejected. - Size cap of 50 MB, checked against
Content-Lengthfor a fast fail and again against the buffered body length. - An empty body is rejected. If your provider cannot return a document mid-signing, returning an error or empty body during a partial or preview read is tolerated, and Ironclad falls back to the unsigned packet. A read after the signature request has completed must return the signed PDF.
- The document is antivirus-scanned on arrival. Infected or unscannable files are rejected (fail-closed).
Updated about 6 hours ago

