DoConure

Salesforce (Apex)

Minimal service class. In production move the URL and key into a Named Credential so the key is not in the code.

public with sharing class DocumentComposer {

    private static final String BASE_URL = 'https://your-host';
    private static final String API_KEY  = 'lpdc_xxxxxxxxxxxxxxxxxxxxxxxxxxxx';

    public static Blob generateDocument(String templateId, Map<String, Object> data) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(BASE_URL + '/api/v1/compose/documents/generate');
        req.setMethod('POST');
        req.setHeader('Authorization', 'Bearer ' + API_KEY);
        req.setHeader('Content-Type', 'application/json');
        req.setBody(JSON.serialize(new Map<String, Object>{
            'data'     => data,
            'settings' => new Map<String, Object>{ 'templateId' => templateId }
        }));
        req.setTimeout(120000);

        HttpResponse res = new Http().send(req);
        if (res.getStatusCode() != 200) {
            // Branch on the code, not the message.
            Map<String, Object> err =
                (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            throw new CalloutException(
                'DoConure ' + err.get('code') + ': ' + err.get('message'));
        }
        return res.getBodyAsBlob();
    }
}

Remember to add the host under Setup → Remote Site Settings, or use a Named Credential, otherwise Salesforce blocks the callout.

Composing and sending for signature in one call — POST /api/v1/flow — is the same shape, just a richer body and a different endpoint. The signature goes where the template has {{ signature.A }}, so the signer needs no coordinates (Where the signature goes on the page: a tag in the document, or fractions of the page from the top-left as the alternative):

public static Map<String, Object> composeAndSign(
        String templateId, Map<String, Object> data,
        String signerEmail, String signerName,
        String requestKey) {  // one per piece of work, e.g. 'contract-' + record.Id
    // Placed by the template's {{ signature.A }} tag. Without a tag, give
    // 'fields' => [{'kind' => 'signature', 'page' => 1, 'x' => 0.1, 'y' => 0.78}].
    Map<String, Object> signer = new Map<String, Object>{
        'email' => signerEmail, 'fullName' => signerName
    };
    Map<String, Object> body = new Map<String, Object>{
        'data'     => data,
        'settings' => new Map<String, Object>{ 'templateId' => templateId },
        'signature' => new Map<String, Object>{
            'subject' => 'Please sign',
            'signers' => new Map<String, Object>{ 'A' => signer },
            // Told when it closes, at your Apex REST class, authenticated by
            // the "prod" callback environment (OAuth2 client credentials).
            'callbackUrl' => new Map<String, Object>{
                'env' => 'prod', 'onSuccess' => '/doconure/ok', 'onError' => '/doconure/ko'
            }
        }
    };

    HttpRequest req = new HttpRequest();
    req.setEndpoint(BASE_URL + '/api/v1/flow');
    req.setMethod('POST');
    req.setHeader('Authorization', 'Bearer ' + API_KEY);
    req.setHeader('Content-Type', 'application/json');
    // Retried Apex callouts (a platform event replay, a batch retry) must not
    // compose and email a signer twice. The key names the work, not the
    // attempt: the same record gives the same key, so a retry is recognised
    // (see /docs/flow). A fresh random key per attempt would defeat it.
    req.setHeader('Idempotency-Key', requestKey);
    req.setBody(JSON.serialize(body));
    req.setTimeout(120000);

    HttpResponse res = new Http().send(req);
    return (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
    // Or poll GET /api/v1/flow/{flowId} until output.status is not "pending".
}

Being called back in your org

Apex REST on your org's own domain wants an OAuth token, so an unauthenticated callback cannot reach it without a Site and a guest user. Instead, add a callback environment in the console (OAuth2 client credentials against your org's token URL, with an External Client App's consumer key and secret) and send env and paths, as above. We fetch the token, keep it until it expires and send it with every callback, beside our HMAC: How do I make our callbacks authenticate at my system?