Quickstart
1. Create an application
In DocBloc, under DocBloc API, create an application. You get a client id, a client secret and a webhook secret — shown once. DocBloc stores only a hash of the client secret, so losing it means rotating it, which stops the old one working immediately.
On the same screen, set whichever of these your integration needs:
- Webhook URL — leave empty and no events are ever delivered.
- Allowed redirect URLs — only for the redirect mode.
- Allowed embed origins — only for framing a DocBloc page (sign page or field configurator) inside your own. Scheme and host, no path.
2. Exchange your credentials for a token
POST /auth
Content-Type: application/json
{ "clientId": "...", "clientSecret": "..." }
200 OK
{ "data": { "accessToken": "eyJ...", "expiresOn": "2026-09-21T12:34:56Z" } }Tokens last 90 minutes. Cache and reuse one rather than exchanging per request — POST /auth is rate limited. DocBloc.Client (.NET) and @docbloc/client (server-side Node) do this caching for you, so everything after this step assumes one of those.
3. Create a template
A template is a PDF plus the fields that go on it. There are two ways to get one, and most integrations only ever need the first:
A. You already know where the fields go. Upload the PDF and describe every field in the same call — no configurator, no coordinate arithmetic if you use anchorText instead of raw x/y:
POST /pdfadmin/template
Content-Type: multipart/form-data
file: agreement.pdf
request: {
"name": "Supplier agreement",
"fields": [
{
"name": "signature",
"type": "Signature",
"recipientIndex": 1,
"anchorText": "{{signature_1}}",
"anchorOffsetY": -10,
"width": 180,
"height": 40
},
{
"name": "signed_date",
"type": "Date",
"recipientIndex": 1,
"anchorText": "{{date_1}}",
"width": 100,
"height": 20
}
]
}anchorText finds that exact text in the PDF and positions the field relative to it — the text must appear exactly once. Without an anchor, position with pageNumber/x/y instead, in PDF points with the origin at the bottom-left of the page. A template needs at least one field, or submissions built from it have nothing to route to a recipient and fail outright.
B. You want a person to place the fields visually — your own admin laying out a template they just uploaded, with no DocBloc login. Upload the plain PDF, mint a configure link for it, and frame the configurator:
POST /pdfadmin
Content-Type: multipart/form-data
file: agreement.pdf
pdfName: Supplier agreement
200 OK
{ "data": { "id": "3f2b...", ... } }
POST /pdfadmin/3f2b.../configure-link
Content-Type: application/json
{ "expiryInMinutes": 60 }
200 OK
{ "data": { "token": "eyJ...", "url": "https://sign-docbloc.com/configure/eyJ..." } }Frame url (or hand token to DocBlocConfigurator — see Iframe embed). The person placing fields needs no DocBloc account at all; the token itself is what authorizes them, scoped to this one template until it expires.
4. Create a submission
var submission = await docBloc.SubmissionAsync(templateId, new CreateSubmissionDTO
{
ExpiryInMinutes = 60 * 24 * 14,
Emails = new Dictionary<int, string> { [1] = signer.Email },
ExternalReference = $"order-{order.Id}",
Metadata = new Dictionary<string, string> { ["branch"] = branch.Name },
FieldValues = new Dictionary<string, string>
{
["signed_date"] = DateTime.UtcNow.ToString("dd/MM/yyyy"),
},
});externalReference is how you find this submission again without storing a mapping table — it comes back on every read and every webhook. Pass an Idempotency-Key header so a retried call returns the original submission instead of creating a second one and emailing the signer twice.
5. Get the signer to the document
Pick one, based on how much of the experience you want to own:
- Do nothing. DocBloc has already emailed the signer a link. This is headless mode — the default, and the least that can go wrong.
- Send them yourself. Read
submission.Data.Workflowfor recipient 1'sLinkand redirect them to it — see Redirect. - Frame it in your own page:
Full write-up, events and the no-dependency version at Iframe embed.import { DocBlocSignature } from '@docbloc/embed' <DocBlocSignature baseUrl="https://sign-docbloc.com" token={signLink} style={{ height: '80vh' }} onCompleted={() => refreshTheRecord()} />
6. Handle 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);
if (e?.Type == "submission.completed" && await _processed.MarkIfNew(e.SubmissionId, e.Type))
{
var signed = await docBloc.SignedAsync(e.ExternalReference);
await _files.Store(signed.Stream, $"{e.ExternalReference}.pdf");
}
return Ok();
}Four rules, all of which matter and are covered in depth on the overview and headless pages:
- Verify the raw request body — a round trip through a JSON parser will not verify.
- Check the timestamp against a tolerance, so a captured delivery can't be replayed.
- Be idempotent — a retried delivery must not advance your record twice.
- Answer fast; do the slow work (fetching the signed PDF) behind the response.
Where to go next
- Headless — the full write-up of step 5's first option.
- Redirect — sending the signer yourself, and what to trust (and not trust) on their return.
- Iframe embed — framing the sign page or the field configurator, with or without
@docbloc/embed.