HOWTO: Integrate Adobe Sign API with Ruby on Rails

Adobe Sign is a product that allows companies to send documents to another individual or company to sign. This product is similar to Docusign and Hellosign. Most signing services provide APIs that allow developers to automate the signing process.

This document describes the steps involved to programmatically send a PDF document with dynamically-filled fields to a user to sign, and receive call-backs when the document is signed (or rejected).

  1. Signup for an Adobe Sign developer account at Please note that they don’t let you register using the same email as an existing Adobe Sign user (Why? I don’t know..)

  2. Once registered and confirmed, you can goto Adobe Sign’s website, log in, and upload a PDF template into their document library. You can add data fields to various locations. Don’t forget to add a signature field and a signature date field.

  3. Before we can start the OAuth process, we need to create a “webhook” on your web application to receive POSTs from Adobe Sign. We need that during the OAuth process to read an OAuth code from them, and also during the signature process to receive call-back when a documents is signed. In Rails, you can do the following to create a simple webhook that prints out the parameters:


      post '/sign_webhook' => 'adobe_sign#sign_webhook'
      get '/sign_webhook' => 'adobe_sign#sign_webhook'


      def sign_webhook
        puts "---------- sign webhook : params -------------------------"
        puts params
        puts "---------- sign webhook : request body -------------------"
        puts "---------- sign webhook : end ----------------------------"


      <%= debug(params) %>
  4. You can test out the webhook by checking: http://localhost:3000/sign_webhook?a=1&b=2. Push this to production. For the rest of the document, I am using “” as the name of our production machine. Make sure you have SSL enabled on your production server. Adobe Sign won’t allow callbacks to go to unsecured urls.

  5. We can begin the OAuth process to get access token. Here’s some doc provided by them, but it sure requires a lot of trial and error to get the whole workflow to work properly, as the documentation is not very easy to follow.

  6. Follow the steps described here to create an app for OAuth. Make sure redirect URL is specified. For this example, we are using Check all the boxes in the “Scopes” section and use “account” as modifier. To perform what we are trying to accomplish in this document, you will need the following scopes:


    Take down the client ID and client Secret as we will need that in subsequent steps.

  7. We are ready to retrieve an access token to begin making API calls. Monitor your production server log to capture the parameters sent to the webhook. In this example, since we are using Heroku, we can simply run heroku logs --tail. Next, goto a web browser and type in the following URL. Make sure all your URL parameters are properly encoded.

  8. When you authenticate and grant access to the app, you should see the following in your production log:

    {"code": "[your_oauth_code]",

    Capture the “code” in the log file and use it in the following step.

  9. Use “curl” or other tools to make the following POST request. I like a Chrome plug-in called Postman as it is very easy to debug and the UI is very intuitive.


    Make sure you keep body empty when you make the POST request. You should get the following response:

        "access_token": "[your_access_token]",
        "refresh_token": "[your_refresh_token]",
        "token_type": "Bearer",
        "expires_in": 3600
  10. The access token is only valid for an hour. That won’t work for us since we will be making server side call non-deterministically. Luckily, the refresh token is valid forever, and we can use the refresh token to get a new access token. So before we send an agreement, we need to grab a new access token. Here’s some Ruby code to get a new access token:

      def self.adobe_sign_access_token
        require 'net/http'
        require 'json'
        refresh_token = "[your_refresh_token]"
        client_id = '[your_client_id]'
        client_secret = '[your_client_secret]'
        @host = ''
        @port = '443'
        @path = "/oauth/refresh"
        @body = "refresh_token=#{refresh_token}&client_id=#{client_id}&client_secret=#{client_secret}&grant_type=refresh_token"
        request =, initheader = {'Content-Type' => 'application/x-www-form-urlencoded'})
        request.body = @body
        http =, @port)
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
        response = http.start { |http| http.request(request) }
        j_response = JSON.parse(response.body)
        if j_response.present?
          puts "[adobe_sign] Access Token = #{j_response["access_token"]}"
          return j_response["access_token"]
        raise "Error getting Adobe Sign Refresh Token"
  11. Before we can send an agreement to sign, we need to retrieve the template ID of the template we uploaded in an earlier step. We can do so by making the following GET request.

      def self.adobe_sign_get_templates
        @host = ''
        @port = '443'
        @path = "/api/rest/v5/libraryDocuments"
        access_token = self.adobe_sign_refresh_token
        request =, initheader = {"Access-Token" => access_token})
        request.body = @body
        http =, @port)
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
        response = http.start { |http| http.request(request) }
        j_response = JSON.parse(response.body)
        ap j_response
  12. Finally, we are all set to send an agreement. The following code shows how the form fields can be pre-filled. In this example, we assumed that there are two fields called “cost” and “vendor_name”, whose value will be passed into the method.

      def self.adobe_sign_send_agreement (email, cost, vendor_name)
        library_document_id = "[your_template_id]"
        # get access token
        access_token = self.adobe_sign_refresh_token
        @host = ''
        @port = '443'
        @path = "/api/rest/v5/agreements"
        @body = {
            documentCreationInfo: {
                fileInfos: [{
                                libraryDocumentId: library_document_id
                name: "Our First Agreement",
                recipientSetInfos: [{
                                        recipientSetMemberInfos: [{email: email}],
                                        recipientSetRole: "SIGNER"}],
                signatureType: "ESIGN",
                signatureFlow: "SENDER_SIGNATURE_NOT_REQUIRED",
                callbackInfo: "",
                mergeFieldInfo: [
                    {defaultValue: cost, fieldName: "cost"},
                    {defaultValue: vendor_name, fieldName: "vendor_name"},
        request =, initheader = {'Content-Type' => 'application/json', "Access-Token" => access_token})
        request.body = @body
        http =, @port)
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
        response = http.start { |http| http.request(request) }
        j_response = JSON.parse(response.body)
        ap j_response

    When you run the method, if everything goes well, you will get the following response with the agreement ID.

      "agreementId": "3AAABLblqZhCE1yk2iufMtVoHyVtjrfMtCFLmYXXyaWP18_eNdnLq3_ifO2A2XUhRq5XiXA3PXwWgOdiPLtQGj3oFLuBgth62"

    You probably want to store it in the database somewhere, and when the agreement is signed by the user, the webhook will get triggered and be able to update the status.

  13. The agreement is sent. When the recipient signs the doc, you will receive a POST to the webhook with the following parameters. Your webhook should grab the status and update your database accordingly.

    {"documentKey"=>"[agreement_id]", "eventType"=>"ESIGNED", "status"=>"SIGNED"}
  14. And that’s it! We’ve now sent an agreement out for signature with pre-filled form fields. When the agreement is signed, our web app receives a callback which could update the database or notify the administrator. Can you think of other use cases for Adobe Sign? Let me know!

  • Pingback: Maria Smith

  • Raymond Liversage

    Thank you for taking the time to write this. I am currently doing a similar integration and the Adobe documentation is very hard to understand. I am a beginner developer and this has been very helpful. :)