APIs & Integrations

DRidenhour
Member

Form submission with PRIVATE file upload

Hello,


I've seen a few posts about this, but none that completely answer my questions.  I am trying to accomplish:

PRIVATE File upload via API -> Pick URLs from the response and submit via form submission API -> Files listed as links in submission view, like they appear when uploading files via the built-in Hubspot forms

I see I can retrieve a signed url via another API call, so I think I need to use that.  So, my questions:

  • Is the signed url necessary to attach private files to a form submission?
  • If so, what happens when these urls expire?  Does Hubspot regenerate them or do I need to create a job on my end to update them?
  • What data do I need to send so the form submission results list files as clickable links, instead of strings?

    Thank you for your help!

    -Dalton
0 Upvotes
4 Replies 4
tjoyce
Recognized Expert | Elite Partner
Recognized Expert | Elite Partner

Form submission with PRIVATE file upload

TBH I'm not familiar with pushing private files directly into the media manager. I have only accessed private files either through retrieving a contact property via API or uploading form submissions via API.

 

When I do the form submissions, I put the file into an AWS s3 bucket with my own signatures in the URL and pass that URL as a prop to the form submission file property and then delete the file from S3.

 

That being said. 

I think what you're trying to do is, use your own form, upload the file to HS file manager with the private flag set, then pass that private file url to the submission file prop. You won't be able to do this because if the file is private the api won't be able to read it... 

 

You need to hit the  the API endpoint to grab the private file url with signature from the file api 

https://legacydocs.hubspot.com/docs/methods/files/get-signed-url-private-file

and then use that url to pass as a prop on the contact properties... 

After that is done, the contact should now have their own signed version of the file in their contact properties and you can probably delete the original file from the file manager

 

 

 

Lg65
Member

Form submission with PRIVATE file upload

Hi @tjoyce @dennisedson , I know it's been a couple of years since this post, but hoping I could reopen the discussion to clarify some things? Per your suggestions, I wrote a script to make POST requests to the Hubspot API to submit a Hubspot form with an attached form by doing the following:

1. Upload a pdf file with a `PRIVATE` access tag, via the file manager upload API endpoint 

2. Get the signed url of the private file I just uploaded, using its `fileId` with this API endpoint

3. Submit a Hubspot form using the formId & portalId and the required form fields, including the signed url of the file I uploaded. That was via this secure form submission API endpoint

4. Delete the file I uploaded (by fileId) to the file manager using this API endpoint

Everything actually seems to be going well and it appears that the file I wanted attached to a contact, as part of the API-based form submission, is indeed there (even after the file was deleted in the file manager). However, there are 2 concerns I have

  1. Even though the file is no longer in the file manager, but a version of it is not a part of the contact record, how exactly is the file part of the contact record now, where is it really stored, and does it have any sort of expiration time?
  2. The file now attached to the contact record has the extension ".unknown" instead of ".pdf". I can download the file from the contact record, and manually change the extension, but this is not at all ideal. Why is the extension changed and how might I change it to remain as the original ".pdf"?  For the record I don't have this issue when I set the file access to PUBLIC and use that url instead of the signed url. It seems mainly to be an issue with using the signed url. I've seen related discussions like this: https://community.hubspot.com/t5/APIs-Integrations/Forms-API-file-submitting-by-URL-incorrect-determ...

 

If it helps at all, here's the Node.js code I wrote to do this:

 

/* eslint-disable no-console */
require(`dotenv`).config();
const axios = require(`axios`);
const FormData = require(`form-data`);
const fs = require(`fs`);

const portalId = process.env.HUBSPOT_PORTAL_ID;
const formId = process.env.HUBSPOT_FORM_ID;
const hubSpotApiKey = process.env.HUBSPOT_API_KEY;

// API Docs: https://legacydocs.hubspot.com/docs/methods/files/v3/upload_new_file
async function uploadFileToHubSpot(filePath) {
  const url = `https://api.hubapi.com/filemanager/api/v3/files/upload`;

  // var filename = 'example_file.txt';

  const fileOptions = {
    access: `PRIVATE`,
    overwrite: false,
    duplicateValidationStrategy: `NONE`,
    duplicateValidationScope: `EXACT_FOLDER`,
    extension: filePath.split(`.`).pop(), // Set the file extension
  };

  const formData = new FormData();
  formData.append(`file`, fs.createReadStream(filePath));
  formData.append(`options`, JSON.stringify(fileOptions));
  formData.append(`folderPath`, `contact_resumes`);

  try {
    const response = await axios.post(url, formData, {
      headers: {
        ...formData.getHeaders(),
        Authorization: `Bearer ${hubSpotApiKey}`,
      },
    });
    // This should include the URL of the uploaded file
    const responseData = response?.data?.objects?.[0];
    // const fileUrl = responseData.url;
    return responseData;
  } catch (error) {
    console.error(`Error uploading file to HubSpot:`, error);
    return null;
  }
}

// API Docs: https://legacydocs.hubspot.com/docs/methods/files/get-signed-url-private-file
async function getSignedURLForPrivateFile(fileId) {
  if (!portalId || !formId || !hubSpotApiKey) {
    console.error(`Missing required environment variables ==> portalId: ${portalId}, formId: ${formId}, hubSpotApiKey: ${hubSpotApiKey}`);
    return null;
  }
  const endpoint = `https://api.hubapi.com/filemanager/api/v3/files/${fileId}/signed-url`;

  try {
    const response = await axios.get(endpoint, {
      headers: {
        'Content-Type': `application/json`,
        Authorization: `Bearer ${hubSpotApiKey}`,
      },
    });

    console.log(`Response from HubSpot:`, response.data);
    const signedUrlResponseData = response?.data;
    return signedUrlResponseData;
  } catch (error) {
    console.error(`Error submitting to HubSpot:`, error.response.data);
    // Handle error
    return null;
  }
}

// API Docs: https://legacydocs.hubspot.com/docs/methods/files/hard_delete_file_and_associated_objects
async function deleteFileByFileIdInFileManager(fileId) {
  if (!portalId || !formId || !hubSpotApiKey) {
    console.error(`Missing required environment variables ==> portalId: ${portalId}, formId: ${formId}, hubSpotApiKey: ${hubSpotApiKey}`);
    return false;
  }
  const endpoint = `https://api.hubapi.com/filemanager/api/v2/files/${fileId}/full-delete`;

  try {
    const response = await axios.post(endpoint, {}, {
      headers: {
        'Content-Type': `application/json`,
        Authorization: `Bearer ${hubSpotApiKey}`,
      },
    });

    console.log(`Response from HubSpot:`, response.data);
    const responseData = response?.data;
    const successStatus = responseData.succeeded === true;
    return successStatus;
  } catch (error) {
    console.error(`Error submitting to HubSpot:`, error.response.data);
    // Handle error
    return false;
  }
}

// API Docs: https://legacydocs.hubspot.com/docs/methods/forms/v2/submit_form_authentication
async function submitFormToHubSpot(jsonFormData, uploadedResumeUrl) {
  if (!portalId || !formId || !hubSpotApiKey) {
    console.error(`Missing required environment variables ==> portalId: ${portalId}, formId: ${formId}, hubSpotApiKey: ${hubSpotApiKey}`);
    return null;
  }
  const endpoint = `https://api.hsforms.com/submissions/v3/integration/secure/submit/${portalId}/${formId}`;

  // const fileMetadata = {
  //   url: uploadedResumeUrl, // Your signed URL
  //   name: `MockDataResum`,
  //   extension: `pdf`,
  //   type: `DOCUMENT`,
  //   size: 36834,
  // };

  // const metadataJsonString = JSON.stringify(fileMetadata);

  if (uploadedResumeUrl) {
    jsonFormData.fields.push({
      name: `upload_resume_pdf`,
      value: uploadedResumeUrl,
      // value: metadataJsonString,
    });
  }
  // const formData = new FormData();

  // formData.append(`fields`, JSON.stringify(jsonFormData.fields));
  // if (jsonFormData.filepath && jsonFormData.filename) {
  //   formData.append(`upload_resume_pdf`, fs.createReadStream(jsonFormData.filepath), {
  //     filename: jsonFormData.filename,
  //   });
  // }
  // if (jsonFormData.context) {
  //   formData.append(`context`, JSON.stringify(jsonFormData.context));
  // }

  // const test = formData.getHeaders();

  try {
    const response = await axios.post(endpoint, jsonFormData, {
      headers: {
        // ...formData.getHeaders(),
        'Content-Type': `application/json`,
        Authorization: `Bearer ${hubSpotApiKey}`,
      },
    });

    console.log(`Response from HubSpot:`, response.data);
    // Handle success
    return response;
  } catch (error) {
    console.error(`Error submitting to HubSpot:`, error.response.data);
    // Handle error
    return null;
  }
}

async function submitToHubSpot(jsonFormData, filePath) {
  const uploadResponseData = await uploadFileToHubSpot(filePath);
  console.log(`Uploaded resume URL response data:`, JSON.stringify(uploadResponseData));
  if (!uploadResponseData || !uploadResponseData.id) {
    console.error(`Failed to upload file to HubSpot --> ${JSON.stringify(uploadResponseData)}`);
    return null;
  }
  const fileId = uploadResponseData.id;
  const signedUrlResponseData = await getSignedURLForPrivateFile(fileId);
  const signedUrl = signedUrlResponseData?.url;
  console.log(`Signed URL:`, signedUrl);
  if (!signedUrlResponseData || !signedUrl) {
    console.error(`Failed to get signed URL for file ID: ${fileId}`);
    return null;
  }
  const submitResponse = await submitFormToHubSpot(jsonFormData, signedUrl);
  if (!submitResponse) {
    console.error(`Failed to submit form to HubSpot`);
    // return null;
  }
  console.log(submitResponse);
  const deleteUploadedFileFromFileManagerSuccess = await deleteFileByFileIdInFileManager(fileId);
  if (!deleteUploadedFileFromFileManagerSuccess) {
    console.error(`Failed to delete uploaded file from FileManager`);
    // return null;
  }
  return 0;
}

const formData = {
  // Replace with the actual fields and values you want to submit
  fields: [
    {
      name: `email`,
      value: `karansuraj@gmail.com`,
    },
    {
      name: `firstname`,
      value: `TEST`,
    },
    {
      name: `lastname`,
      value: `TEST`,
    },
    // ... other fields
  ],
  // // context is optional, can include additional data like `hutk` (HubSpot tracking cookie)
  // context: {
  //   hutk: `...`,
  //   pageUri: `www.example.com/form-page`,
  //   pageName: `Form Page`,
  // },
};

const filepath = `/Users/karansuraj/OutcoRepos/apply_pass_backend/MockDataResum.pdf`;
submitToHubSpot(formData, filepath);

module.exports = {
  submitToHubSpot,
};

/*
 Useful Guidelines here: https://community.hubspot.com/t5/APIs-Integrations/Form-submission-with-PRIVATE-file-upload/td-p/413357?profile.language=fr
*/

 

 

0 Upvotes
DRidenhour
Member

Form submission with PRIVATE file upload

@tjoyce Sorry for the delay; didn't see your response!  Thank you for the feedback.  I still have a couple of questions:

1.  Is there a way for us to set the expiry time for the signed url?  I want the files to be available for a long period of time.
2.  Is there a way to have the links show up as clickable urls in the admin, like they do when we submit via a standard Hubspot form?  I can only get them to appear as strings, which is cumbersome.

Thank you!

-Dalton

0 Upvotes
dennisedson
HubSpot Product Team
HubSpot Product Team

Form submission with PRIVATE file upload

Hello @DRidenhour 

Going to set @tjoyce loose on this one.  Might need to work with @tjoyce and create a how to video for things like this 🐹

0 Upvotes