Updating envelope custom fields using Go

Let’s go on with Go! In this post, I’ll show you how to update custom envelope fields. DocuSign code example history buffs will recognize that I am using the How to Set Tab values example mixed with using the EnvelopeCustomFields:update API method. However, to use this method, you need to provide the custom field tab ID so that the eSignature REST API can determine which tab value needs to be updated. 

What is the difference between tabs and custom fields?

Envelopes sent from DocuSign tend to have documents attached, on which you're going to collect a signature and perhaps some data.  Those various text and input boxes you see on your document for collecting a signature are known as tabs; tabs are used at the document level. 

Conversely, custom fields are input boxes for either text or arrays (also commonly referred to as lists [the key difference is that a list is an ORDERED array] in various programming language disciplines). In this example I’m not setting a custom field value for a field within a given document; I’m creating a custom field that lives on the envelope itself, in order to store transaction information—in this case, a field for a given customer type as well as for a tracking number. 

To get started, I'll create an envelope definition with a small Base64 string of a text file, an email subject, an array of signers, an array of custom fields, and your envelope status. I've also added in a small bit of code to generate the current date when the envelope is sent:



func makeCustomFieldsEnvelope(signerEmail string, signerName string, customerType string ) string {
 
    // You may perhaps like this style instead?
    // https://go.dev/play/p/Ik8-Mh3BBH4


    dateTimeString := time.Now().Format("01-02-2006")
 
    envelope := fmt.Sprintf(`{
    "emailSubject": "Please sign this document set",
    "documents": [{
        "documentBase64": "DQoNCg0KCQkJCXRleHQgZG9jDQoNCg0KDQoNCg0KUk0gIwlSTSAjCVJNICMNCg0KDQoNClxzMVwNCg0KLy9hbmNoMSANCgkvL2FuY2gyDQoJCS8vYW5jaDM=",
        "documentId": "1",
        "fileExtension": "txt",
        "name": "Order Summary %s"
    }],


            "customFields": {
                "textCustomFields": [{
                    "name": "customerType",
                    "required": "true",
                    "show": "true",
                    "value": "%s"
                },
                {
                    "name": "trackingNumber",
                    "required": "false",
                    "show": "true",
                    "value": ""
                },
                {
                    "name": "shippingDate",
                    "required": "false",
                    "show": "true",
                    "value": ""
                }]
    "recipients": {
        "signers": [
            {
                "email": "%s",
                "name": "%s",
                "recipientId": "1",
                "routingOrder": "1",
                "tabs": {
                    "signHereTabs": [{
                        "documentId": "1",
                        "name": "SignHereTab",
                        "pageNumber": "1",
                        "recipientId": "1",
                        "tabLabel": "SignHereTab",
                        "xPosition": "75",
                        "yPosition": "572"
                    }]
                },
            }
        ]
    },
    "status": "sent"
}`, dateTimeString, signerEmail, signerName, customerType)
 
    return envelope
}

Retrieve your custom field ID

To update the custom fields, you’ll need to retrieve their given ID. Retrieving these custom field IDs is as simple as a small GET call:

// Send an envelope
func getCustomFields(DSAccessToken string, DSAccountId string, envelopeId string) ([]string, error) {
    client := &http.Client{}
    // Use http.NewRequest in order to set custom headers
    req, err := http.NewRequest("GET", "https://demo.docusign.net/restapi/v2.1/accounts/"+DSAccountId+"/envelopes/"+envelopeId+"/customfields", nil)
    req.Header.Set("Authorization", "Bearer "+DSAccessToken)
    if err != nil {
        fmt.Printf("Request Failed: %s", err)
        return []string{""}, err
    }
    // Since http.NewRequest is being used, client.Do is needed to execute the request
    res, err := client.Do(req)
    if err != nil {
        fmt.Printf("Request Failed: %s", err)
        return []string{""}, err
    }
 
    body, err := io.ReadAll(res.Body)
    if err != nil {
        fmt.Printf("Request Failed: %s", err)
        return []string{""}, err
    }
    fmt.Printf("Body: %s\n", body)
    // Decode the response to JSON
    var customFields CustomFields
    jsonErr := json.Unmarshal(body, &customFields)
    if jsonErr != nil {
        fmt.Printf("Request Failed: %s", jsonErr)
        return []string{""}, jsonErr
    }
    responseArray := []string{customFields.TextCustomFields[0].FieldID, customFields.TextCustomFields[1].FieldID}
    return responseArray, nil
}

There is a bit of happy-path development happening here. I’m looking through the JSON response and returning the first two custom field IDs.  I'm aware that the first value is the tracking data and that the second value is the shipping date, something fairly straightforward if you're only working with one field; but when working with multiple fields, I suggest building a mechanism to search the field names for a given value and to retain the corresponding custom field ID accordingly. Speaking of which, make sure you include your CustomFields struct so that you can parse the resulting JSON. I stored mine at the very top, just before initializing variables:

// Auto-generated using https://transform.tools/json-to-go
type CustomFields struct {
    TextCustomFields []struct {
        FieldID  string `json:"fieldId"`
        Name     string `json:"name"`
        Show     string `json:"show"`
        Required string `json:"required"`
        Value    string `json:"value"`
    } `json:"textCustomFields"`
    ListCustomFields []interface{} `json:"listCustomFields"`
}

If you use this combination of setting custom fields on envelope generation and keeping those fields updated and maintained with an API interfacing script, then, so long as you keep a manageable system, you should be able to reliably set envelope metadata, such as payments, third-party logistics, or heuristics telemetry data, with the envelope itself. Furthermore, you can use the custom field as an index query: if you keep a format such as code designations within your messages, you can query for a given customer ID or type so that you can pull all results (or lack thereof). 

For this example, you can set the shipping date and tracking number using arbitrary strings; I have set the date shipped to be three days from the current date. With these values, I'll update a small bit of JSON to populate those textCustomfields on that envelope and return the output response to the end user:

// Sets custom field values for an envelope
func setCustomfieldValues(DSAccessToken string, DSAccountId string, envelopeId string, trackingNumberFieldId string, shippingDateFieldId string) (string, error) {
    client := &http.Client{}


    // For the sake of the example these are arbitrary values,
    // on a real application these values should be retrieved
    // from an orders table on a database
    shippingDate := time.Now().Add(time.Hour * 24 * 3).Format("01-02-2006")
    trackingNumber := "1Z" + rand_str(16)


    requestBody := fmt.Sprintf(`{ textCustomFields: [
        { "fieldId" : "%s",
          "value"  : "%s" },
          { "fieldId" : "%s",
          "value"  : "%s" }


        ]}`, trackingNumberFieldId, trackingNumber, shippingDateFieldId, shippingDate)


    // Use http.NewRequest in order to set custom headers
    req, err := http.NewRequest("PUT", "https://demo.docusign.net/restapi/v2.1/accounts/"+DSAccountId+"/envelopes/"+envelopeId+"/custom_fields", strings.NewReader(requestBody))
    req.Header.Set("Authorization", "Bearer "+DSAccessToken)
    if err != nil {
        fmt.Printf("Request Failed: %s", err)
        return "", err
    }
    // Since http.NewRequest is being used, client.Do is needed to execute the request
    res, err := client.Do(req)
    if err != nil {
        fmt.Printf("Request Failed: %s", err)
        return "", err
    }


    body, err := io.ReadAll(res.Body)
    if err != nil {
        fmt.Printf("Request Failed: %s", err)
        return "", err
    }
    // fmt.Printf("Body: %s\n", body)


    return string(body), nil
}

That's it, folks! Use this humble snippet to complete your MVP. Possible enhancements to this code might include more functions to pull data sources into a temporary object or table and to then do some for-loop operations so that you can process them at scale. For example, you could use Envelopes:listStatusChanges to fetch a list of envelope IDs, and then proceed to update each one with the correlated tracking and shipping data. You can find the complete example in this GitHub. To call the code snippets made earlier, drop them into the main function for your Go app:

func main() {

    //  ... OAuth still necessary for Access Tokens

    envelopeDefinitionCustomTabs := makeCustomFieldsEnvelope("sonic@example.com", "sonic the hedgefrog", "CUSTOMER-"+rand_str(14))
    // fmt.Printf(envelopeDefinitionCustomTabs)

    envelopeId2, err := sendEnvelope(dSAccessToken, dSAccountId, envelopeDefinitionCustomTabs)
    if err != nil {
        fmt.Printf("update custom fields request failed: %s", err)
    }
    // fmt.Print("envelope ID is: " + envelopeId2 + "\n")

    // Lets retrieve the Envelope custom Fields ids
    customFieldIds, err := getCustomFields(dSAccessToken, dSAccountId, envelopeId2)

    // Set the shipping date and tracking number on the envelope
    finalRes, err := setCustomfieldValues(dSAccessToken, dSAccountId, envelopeId2, customFieldIds[0], customFieldIds[1])
    fmt.Printf("Envelope Custom fields updated successfully: \n%s", finalRes)
}

Stay tuned for more cool stuff from us, and thanks for hanging out with me today!

Your API brother-from-another-mother,

Aaron Jackson-Wilde

PS: Click one of the links 👇👇👇 for more tips and tricks to use the eSignature API!

Additional resources

Aaron Wilde
Author
Aaron Jackson-Wilde
Programmer Writer
Published