Search by

esigner / sdk

steffanhalv

Official PHP client for the esigner e-signing API: BankID, MitID and other eID signature requests, signed PDFs and verified webhooks.

v0.2.0 2026-10-14 12:52 UTC

This package is auto-updated.

Last update: 2026-11-04 12:05:53 UTC


README

esigner

esigner SDK for PHP

Send PDFs for signing with BankID, MitID and other national eIDs, get the signed PDF back, and verify webhooks — from PHP and WordPress.

Packagist version PHP version MIT license

API reference · esigner.no · esigner.dk · esigner.se · esigner.fi · esigner.de · esigner.me

  • Only ext-curl and ext-json — drops into WordPress or any framework
  • Safe retries: every POST carries an idempotency key that stays the same across retries
  • Cursor pagination as generators, webhook verification in one call

Install

composer require esigner/sdk

PHP 8.1 or newer. Create an API key under Konto → Utvikler on your esigner site. Keys starting with sk_test_ sign against the test environment: the real flow, with test identities, no payment and no legally binding signature.

Send a document for signing

use Esigner\EsignerClient;

// ['market' => 'se'] for esigner.se — see the table below
$esigner = new EsignerClient(getenv('ESIGNER_API_KEY'));

$request = $esigner->signatureRequests->createWithFiles('/path/to/contract.pdf', [
    // The sender: receives the receipt and the signed PDF
    'ownerEmail'      => 'post@company.no',
    'signers'         => [
        ['name' => 'Ola Nordmann', 'email' => 'ola@example.no'],
        ['name' => 'Kari Nordmann', 'email' => 'kari@example.no'],
    ],
    // Draw from your plan's quota
    'useSubscription' => true,
    // Optional: Norwegian BankID only
    'signingEids'     => ['no_bankid'],
    // Optional: where signers land after the eID step
    'returnUrl'       => 'https://example.com/contracts/123/signed',
]);

echo $request['signers'][0]['sign_url'];

Files can be paths or bytes — ['data' => $pdfBytes, 'filename' => 'contract.pdf'] — and several are merged in the order given. Or upload first and create separately:

$document = $esigner->documents->upload(
    ['a.pdf', 'b.docx'],
    ['ownerEmail' => 'post@company.no'],
);
$request = $esigner->signatureRequests->create([
    'documentId' => $document['id'],
    'signers'    => [/* … */],
]);

Using another market? new EsignerClient($key, ['market' => 'se']) — see the table below.

Follow it up

// status, signers, …
$request = $esigner->signatureRequests->retrieve($id);

if ($request['status'] === 'completed') {
    // The PDF's bytes
    $pdf = $esigner->signatureRequests->downloadSignedDocument($id);
    file_put_contents('signed.pdf', $pdf);
}

// Before anyone has signed
$esigner->signatureRequests->cancel($id, ['reason' => 'Wrong version']);

// Several files → one entry per position
$esigner->signatureRequests->listSignedDocuments($id);

Balance and events

// ['balance_ore' => …, 'currency' => …]
$esigner->balance->retrieve();

foreach ($esigner->balance->allTransactions() as $tx) {
    // every page, newest first
}

foreach ($esigner->events->all(['type' => 'document.completed']) as $event) {
    // the same events your webhook receives — for backfilling after downtime
}

Webhooks (WordPress example)

esigner signs every delivery with Esigner-Signature: t=<unix>,v1=<hmac>. Verify against the raw body — a re-encoded JSON body will not match.

use Esigner\SignatureVerificationException;
use Esigner\Webhook;

add_action('rest_api_init', function () {
    register_rest_route('mysite/v1', '/esigner', [
        'methods'             => 'POST',
        'permission_callback' => '__return_true',
        'callback'            => function (WP_REST_Request $req) {
            try {
                $event = Webhook::constructEvent(
                    $req->get_body(),                          // the raw body
                    $req->get_header('esigner_signature'),
                    getenv('ESIGNER_WEBHOOK_SECRET'),          // whsec_…
                );
            } catch (SignatureVerificationException $e) {
                return new WP_REST_Response(null, 400);
            }
            switch ($event['type']) {
                case 'signer.signed':
                    // $event['data']['signersSigned'] of ['signersTotal']
                    break;
                case 'document.completed':
                    // downloadSignedDocument($event['data']['orderId'])
                    break;
            }
            return new WP_REST_Response(null, 200);
        },
    ]);
});

Webhook::EVENT_TYPES lists them all. Deliveries are retried; dedupe on $event['id'].

Errors

Any non-2xx answer throws Esigner\EsignerException with the API's stable code:

use Esigner\EsignerException;

try {
    $esigner->signatureRequests->create([/* … */]);
} catch (EsignerException $e) {
    if ($e->errorCode === 'quota_exceeded') { /* … */ }
    // also $e->status, $e->detail and the full decoded $e->body
}

Network errors, 429 and 5xx are retried twice with backoff.

Options

new EsignerClient($key, [
    'market'      => 'no',     // which esigner site the key belongs to
    'base_url'    => null,     // or a full API base, e.g. a staging host
    'max_retries' => 2,
    'timeout'     => 60,       // seconds per attempt
]);

Every POST takes a last $opts argument: ['idempotency_key' => '…'] to make a retry from your side return the original result too, and ['timeout' => 120] for one slow call.

Upgrading from 0.1

Esigner\Client and its flat methods (uploadDocument, createSignatureRequest, …) still work and are deprecated; EsignerClient with its services is the way forward.

esigner around the world

esigner runs one site per country, each signing with that country's own eID. An API key belongs to the site it was created on — pick it with the market option.

Site Signers identify with API reference
🇳🇴 Norway esigner.no BankID esigner.no/dokumentasjon
🇩🇰 Denmark esigner.dk MitID esigner.dk/dokumentation
🇸🇪 Sweden esigner.se BankID esigner.se/dokumentation
🇫🇮 Finland esigner.fi Bank credentials (FTN) esigner.fi/dokumentaatio
🇩🇪 Germany esigner.de Personalausweis esigner.de/dokumentation

📱 On the go: esigner.me — the esigner app for iPhone and Android. Scan or upload a document, send it for signing and follow it until everyone has signed.

License

MIT