Signed document field data
When every party has signed, Loyva captures the values that were actually filled into
the document and delivers them on the envelope.completed webhook.
This is the payload most integrations are built around: it lets you write the signed terms
straight into your CRM without re-reading the PDF.
The same data is stored on the envelope, so a delivery you miss is never lost.
Where it appears
envelope.completed carries the captured fields under data.fields:
{
"event_type": "envelope.completed",
"event_id": "evt_9f3a2b1c4d5e6f7a8b9c0d1e",
"org_id": "org_abc123",
"envelope_id": "env_x7k9m2p4q1w3",
"timestamp": "2026-08-27T15:04:05.000Z",
"data": {
"envelope_id": "env_x7k9m2p4q1w3",
"envelope_name": "Monitoring Agreement",
"customer_id": "cust_abc123",
"completed_at": "2026-08-27T15:04:05.000Z",
"docuseal_submission_id": "1234",
"signers": [
{ "email": "[email protected]", "name": "Ada Lovelace", "role": "Buyer", "completed_at": "2026-08-27T15:03:58.000Z" }
],
"fields": [
{
"name": "Monthly Rate",
"type": "number",
"value": 49.99,
"signer_role": "Buyer",
"field_id": null,
"submitter_uuid": "8f14e45f-ea0b-4a1e-9b6d-2c3d4e5f6a7b"
},
{
"name": "Sign Here",
"type": "signature",
"value": null,
"attachments": [{ "uuid": "a1b2c3d4e5f6", "content_type": "image/png" }],
"signer_role": "Buyer",
"field_id": null,
"submitter_uuid": "8f14e45f-ea0b-4a1e-9b6d-2c3d4e5f6a7b"
}
],
"fields_truncated": false
}
}
Field shape
| Key | Type | Description |
|---|---|---|
name | string | The field name as authored on the template. Not unique — two signers can each have a "Full Name" field. |
type | string | Canonical field type. See the vocabulary below. |
value | string | number | boolean | string[] | null | The filled value, coerced by type. Always null for attachment-backed types. |
attachments | array | Present only for attachment-backed types. Metadata only: { uuid, filename?, content_type? }. |
signer_email | string | null | Which signer supplied the value. |
signer_role | string | null | The signer's template role, e.g. Buyer. |
field_id | string | null | Set on Loyva-native e-sign documents. null for DocuSeal. |
submitter_uuid | string | null | DocuSeal submitter id. null for native e-sign. |
Because name is not unique, key on field_id when present, otherwise on
(submitter_uuid, name). Keying on name alone silently collapses one signer's answers
into another's.
Type vocabulary
text · number · date · checkbox · select · multiselect · radio ·
signature · initials · image · file · stamp · phone · payment · other
Coercion rules:
checkbox→ boolean ("true","yes","on","1"are all true)number→ JSON number where parseable, tolerating currency formatting like"$1,299.00"; falls back to the raw stringmultiselect→ array of strings- everything else → string
- an unrecognised template field type maps to
otherand is delivered as a string
Signature, image, and file fields carry metadata only
For signature, initials, image, file, and stamp, value is always null and the
field carries an attachments array instead.
This is deliberate. A signature is biometric-adjacent, and a file field holds whatever the
signer attached — a driver's licence, a voided cheque. Inlining those bytes would place them
permanently in your logs and in Loyva's own delivery records. The field row still tells you
the signature was applied; fetch the artifact itself over an authenticated API call when you
genuinely need it.
Limits
A document cannot produce an unbounded webhook body:
| Limit | Value | Behaviour on overflow |
|---|---|---|
| Fields per envelope | 500 | Extra fields dropped |
| Characters per value | 4000 | Value truncated |
If either limit is hit, data.fields_truncated is true. Treat that as a signal to fetch
the envelope directly rather than trusting the payload as complete.
Coverage
| Signing path | Field capture |
|---|---|
| DocuSeal e-sign | Full, at submission.completed |
| Loyva native e-sign | Full, accumulated per signer |
| Uploaded / scanned paper | None — there are no structured fields to capture |
| DocuSign, Adobe Acrobat Sign | Not captured. Those documents are mirrored read-only and Loyva does not request tab/form data from either provider. |
Handling the payload
app.post('/webhooks/loyva', async (req, res) => {
// Verify the signature first — see /webhooks/verification
res.status(200).json({ received: true })
const { event_type, data } = req.body
if (event_type !== 'envelope.completed') return
if (data.fields_truncated) {
// Payload is incomplete — read the envelope instead of trusting it.
await refetchEnvelope(data.envelope_id)
return
}
const byKey = new Map(
data.fields.map(f => [f.field_id ?? `${f.submitter_uuid}:${f.name}`, f]),
)
await crm.updateContract(data.customer_id, {
monthlyRate: byKey.get('...')?.value,
})
})
Next steps
- Events — the full event catalog
- Verification — verify the signature before trusting a payload