Headless
When to use it
When signing is something that happens to a record rather than a step someone is walking through in your product. A compliance document, a supplier agreement, a policy acknowledgement. It is the least code of any mode and the least that can go wrong.
1. Create the submission
Reference your own record with externalReference. DocBloc indexes it, returns it on every read and carries it in every webhook — it is how the two systems reconcile without you storing a mapping table, though storing the submission id as well means you can go in either direction.
var submission = await docBloc.SubmissionAsync(templateId, new CreateSubmissionDTO
{
ExpiryInMinutes = 60 * 24 * 14,
Emails = new Dictionary<int, string> { [1] = employee.Email },
ExternalReference = $"compliance-{record.Id}",
Metadata = new Dictionary<string, string> { ["branch"] = branch.Name },
// Anything you already know. Only the signature genuinely needs collecting.
FieldValues = new Dictionary<string, string>
{
["employee_name"] = employee.FullName,
["issued_date"] = DateTime.UtcNow.ToString("dd/MM/yyyy"),
},
});The submission comes back ready to sign, with the first recipient already active. DocBloc has already emailed them — you do not need to send anything.
2. Receive the webhook
[HttpPost("webhook")]
[AllowAnonymous]
public async Task<IActionResult> Receive()
{
using MemoryStream buffer = new();
await Request.Body.CopyToAsync(buffer);
byte[] rawBody = buffer.ToArray();
if (!_verifier.IsValid(rawBody,
Request.Headers[DocBlocWebhookVerifier.SignatureHeader],
Request.Headers[DocBlocWebhookVerifier.TimestampHeader]))
{
return Unauthorized();
}
var e = JsonSerializer.Deserialize<DocBlocEvent>(rawBody);
// Ignore what you do not recognise rather than failing on it - DocBloc may add event types,
// and a 4xx makes it retry something you simply do not want.
if (e?.Type is "submission.completed" or "submission.declined")
{
_queue.Enqueue(() => Handle(e));
}
return Ok();
}Answer promptly and do the work behind it. Fetching the signed PDF and storing it is far too slow to do on the request thread.
3. Take custody of the document
var signed = await docBloc.SignedAsync(externalReference);
await _files.Store(signed.Stream, $"compliance-{record.Id}.pdf");
record.FileReferenceId = fileReference.Id;This step is not optional. See the warning at the top of this page.
Being idempotent
A retried delivery must not advance the record twice. The simplest correct approach is to record which events you have processed and skip repeats:
if (await _processed.Contains(e.SubmissionId, e.Type))
{
return;
}
// ... do the work
await _processed.Record(e.SubmissionId, e.Type);