Embedded Signing with Document Upload


This page demonstrates creating an embedded signing session using data submitted from the user.

  1. The user fills in the form below. On clicking Submit, the form data is POST'ed (via an AJAX request) to the server.
  2. The server receives the POST request, and uses the submitted data to create a new Bundle for embedded signing, using the BlueInk API PHP Client Library.
  3. The server returns the embedded signing URL.
  4. The BlueInk Embedded Signing JS library uses the embedded signing URL to create a new embedded signing session.

You can find a fuller breakdown, with code samples, below.

Signing Data

Enter data that will be used to pre-fill the document for this demo. If you provide an email, the completed document will be emailed to you. Data you enter is not used for any other purpose.

Company Name is required.

After clicking submit, the embedded signing iFrame will show below.

Embedded signing iFrame will be placed here

How It Works

Frontend Processing

When the user clicks submit on the form above, we grab the submitted form data (line 4) and POST it to the server (line 7) with the axios HTTP client.

The server then processes the submitted data, creates a new Bundle and returns an embedded signing URL. See below for additional details of the server-side processing.

In the success handler for the request (line 9) we create a new instance of a BlueInkEmbed (line 11), using our public API key to identify our BlueInk Account. We then mount the embedded signing iFrame in the element identified by the selector #iframe-container (line 12). The replace option tells the embed instance to replace the contents of the container, as opposed to appending, which is the default.

And that's it! The embedded signing iFrame then loads and the user can review and sign the document.

Note, this example uses axios for async HTTP requests and jQuery for some basic DOM manipulation. Those libraries are used for illustrative purposes only. The blueink-embed-js library can be used with whatever frontend libraries you prefer: React, Angular, etc.

$('#formExample1').on('submit', function(ev) {

    const fd = new FormData(ev.target);

    // Submit data with axios
    axios.post('/embed/from_upload', fd)
        .then(response => {
            try {
                const embedUrl = response.data.embedUrl;
                const embed = new BlueInkEmbed('public_abcd1234567890abcd1234567890abcd123');
                embed.mount(embedUrl, '#iframe-container', { replace: true });
            } catch(error) {
        .catch(error => {
            // Catch any error returned by our request to the Symfony example app

Note: see the HTML source of this page for the full source code.

Server-side Processing

Note, this example is using the Symfony web framework for processing the AJAX request and returning a result, but any web framework (or plain PHP) will do.

Handling the Request

See comments in the source code below.

 * Route handler that gets called when data is POST'ed to /embed/from_upload.
 * Creates a BlueInk signing Bundle, and returns the URL for an embedded signing session.
 * Note that the uploaded file is not provided in the POST data. Instead, it is a pre-existing
 * file on a remote server, which will be fetched by its URL.
 * In this example, we are using the Symfony framework, which does some automagic things
 * like dependency injection, and setting up this route handler with the @Route comment below.
 * Those details are not important for purposes of this example, but checkout the Symfony
 * framework to learn more.
 * @Route("/embed/from_upload", methods={"POST"})
public function embed_from_upload(Request $request, LoggerInterface $logger, BlueInkHelper $blueInkHelper)
    // Get the $apiKey and $apiUrl. The $apiKey is private and should never be
    // shared. In this example project it is stored in the environment, which is good
    // practice for keeping App secrets private. We allow the API URL to be overridden
    // as well, but in most deployments you can use the default BlueInk API URL.
    $apiKey = $this->getParameter('app.blueink_api_key');
    $apiUrl = $this->getParameter('app.blueink_api_url');

    // Extract data uploaded in the request.
    $name = $request->request->get('name');
    $email = $request->request->get('email');
    $company = $request->request->get('company');
    $title = $request->request->get('title');
    $color = $request->request->get('color');

    // We need an email to send final documents to, so for demo purposes we grab
    // a default if no email was provided.
    if (!$email) {
        $email = $this->getParameter('app.blueink_default_signer_email');

    try {
        // This method handles the actual creation of the Bundle and
        // retrieving the embedded signing URL. See BlueInkHelper.php
        // for details.
        $embedUrl = $blueInkHelper->createBundleFromDocument($apiKey, $apiUrl, $email, $name, $company, $title, $color);
    } catch (RequestException $e) {
        // createBundleFromDocument() can throw a RequestException, indicating
        // one of the requests to the BlueInk API failed

        // Try to get the response, so we can provide more details
        // on any error that occurred.
        $response = $e->getResponse();
        if ($response) {
            $status = $response->getStatusCode();

            // Error details are returned as JSON in the response body.
            // The error details should point you to the specific issue
            // with a failed BlueInk API request.
            $responseBody = $response->getBody();

            $errorData = [
                'error' => "BlueInk API request failed with status code $status"
        } else {
            $logger->warning('No response from BlueInk API');

            $errorData = [
                'error' => "BlueInk API request failed. No response when attempting to connect to $apiUrl"

        return $this->json($errorData, 503);

    // Return the embedded signing URL so that the frontend can
    // create an embedded signing iFrame.
    return $this->json(['embedUrl' => $embedUrl]);

Creating the Bundle and Retrieving the Embedded Signing URL

See comments in the code below.

// BlueInk\ApiClient\Client is provided blueink-client-php library.
// See https://github.com/blueinkhq/blueink-client-php
use BlueInk\ApiClient\Client;
use GuzzleHttp\Exception\RequestException;

class BlueInkHelper
    const DOC_URL = 'https://blueink.com/wp-content/uploads/shared/example-doc-for-1-signer.pdf';

     * Create a new Bundle using a document located at DOC_URL.
     * @param $apiKey the private API key used to make authenticated requests to the BlueInk API
     * @param $apiUrl the BlueInk API URL, used to initialize the API client
     * @param $email The signers email
     * @param '' $name The signers name
     * @param '' $company The company name to use as an initial value in the Bundle
     * @param '' $title signer title to use as an initial value in the Bundle
     * @param '' $color signer's favorite color to use as an initial value in the Bundle
     * @return {string} the embedded signing URL
     * @throws RequestException if a request to the BlueInk API fails or returns a 4xx error
    public function createBundleFromDocument(
        $apiKey, $apiUrl, $email, $name = '', $company = '', $title = '', $color = ''
    ) {
        // Setup the requestData that will be submitted to the BlueInk API
        // to create the new Bundle.
        $requestData = [
            'label' => 'A Test Bundle',
            'is_test' => true,
            // Setup signer information.
            'packets' => [
                    'name' => $name ? $name : 'Anonymous Signer',
                    'email' => $email,
                    'key' => 'signer-1',
                    // This is how we designate that this signer will sign in an embedded
                    // signing session. Other options for delivery are 'email' or 'phone'.
                    'deliver_via' => 'embed',
            // The Bundle will have 1 document, which is fetched from DOC_URL.
            // The document has 5 fields. Initial values are set for all of those
            // fields, except the Initials field which cannot have a value.
            'documents' => [
                    'key' => 'doc-01',
                    // The document will be fetched from a remote URL. Alternatively, the
                    // document could be uploaded as part of this request.
                    'file_url' => BlueInkHelper::DOC_URL,
                    'fields' => [
                            // Every field needs a unique key.
                            'key' => 'field-01',
                            'kind' => 'txt',
                            // 'editors' assigns the field to a signer. 'signer-1' matches
                            // the 'key' used when defining 'packets' above.
                            'editors' => ['signer-1'],
                            // This sets the initial value for this field. If $name is null or
                            // blank, no initial value will be set.
                            'initial_value' => $name,
                            'label' => 'Please enter your name',
                            'required' => false,
                            // These coordinates place the field on the document. The document
                            // uses a coordinate system of 0 to 100 from the lower left corner
                            // of the document.
                            'page' => 1,
                            'x' => 30,
                            'y' => 77,
                            'w' => 20,  // width of the field
                            'h' => 2,   // height of the field
                            'key' => 'field-02',
                            'kind' => 'txt',
                            'editors' => ['signer-1'],
                            'initial_value' => $company,
                            'label' => 'Enter your company',
                            'required' => false,
                            'page' => 1,
                            'x' => 30,
                            'y' => 73.4,
                            'w' => 20,
                            'h' => 2,
                            'key' => 'field-03',
                            'kind' => 'txt',
                            'editors' => ['signer-1'],
                            'initial_value' => $title,
                            'label' => 'Enter your title',
                            'required' => false,
                            'page' => 1,
                            'x' => 30,
                            'y' => 69.5,
                            'w' => 20,
                            'h' => 2,
                            'key' => 'field-04',
                            'kind' => 'txt',
                            'editors' => ['signer-1'],
                            'initial_value' => $color,
                            'label' => 'Your favorite color',
                            'required' => true,
                            'page' => 1,
                            'x' => 30,
                            'y' => 65.8,
                            'w' => 20,
                            'h' => 2,
                            'key' => 'field-05',
                            'kind' => 'ini',
                            'editors' => ['signer-1'],
                            'label' => 'Enter your initials here, if you like:',
                            'required' => false,
                            'page' => 1,
                            'x' => 14,
                            'y' => 52.3,
                            'w' => 8,
                            'h' => 5,

        // Initialize the BlueInk API Client. The $apiUrl is not required, and defaults
        // to https://api.blueink.com/api/v2
        $client = new Client($apiKey, [ 'baseUri' => $apiUrl ]);

        // Create the Bundle. This immediately returns a Bundle, but that
        // Bundle is likely in a pending state, while the document is being processed.
        // However, we can immediately fetch the embedded signing URL.
        $newBundle = $client->bundles->create(['json' => $requestData]);

        // Get the ID of the Packet, which we will use
        // to retrieve an embedded signing URL. A Packet holds data related to a
        // signle signer on the Bundle
        $packetId = $newBundle->packets[0]->id;

        // Retrieve the embedded signing URL
        $responseData = $client->packets->embedUrl($packetId);
        $embedUrl = $responseData->url;

        return $embedUrl;

More Resources