Skip to main content

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_email": "[email protected]",
"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_email": "[email protected]",
"signer_role": "Buyer",
"field_id": null,
"submitter_uuid": "8f14e45f-ea0b-4a1e-9b6d-2c3d4e5f6a7b"
}
],
"fields_truncated": false
}
}

Field shape

KeyTypeDescription
namestringThe field name as authored on the template. Not unique — two signers can each have a "Full Name" field.
typestringCanonical field type. See the vocabulary below.
valuestring | number | boolean | string[] | nullThe filled value, coerced by type. Always null for attachment-backed types.
attachmentsarrayPresent only for attachment-backed types. Metadata only: { uuid, filename?, content_type? }.
signer_emailstring | nullWhich signer supplied the value.
signer_rolestring | nullThe signer's template role, e.g. Buyer.
field_idstring | nullSet on Loyva-native e-sign documents. null for DocuSeal.
submitter_uuidstring | nullDocuSeal 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 string
  • multiselect → array of strings
  • everything else → string
  • an unrecognised template field type maps to other and 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:

LimitValueBehaviour on overflow
Fields per envelope500Extra fields dropped
Characters per value4000Value 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 pathField capture
DocuSeal e-signFull, at submission.completed
Loyva native e-signFull, accumulated per signer
Uploaded / scanned paperNone — there are no structured fields to capture
DocuSign, Adobe Acrobat SignNot 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