Accounting Extension

Access and test APIs in beta. 

Please note: This API is currently in public beta and is subject to change based on testing and feedback. By using these endpoints you agree to adhere to our Developer Developer BetaTerms of Use. You also acknowledge and understand the risk associated with testing an unstable API. 

This API is currently in beta. 

Introduction

On this page, we walk you through the steps of getting your integration working with the HubSpot accounting extension

Create your app definition on HubSpot

In order for your integration to connect to a HubSpot user's account, you must create an app definition on HubSpot for it.  Here, you enter details such as the logo and text to be shown to the HubSpot user when your integration attempts to make an initial connection to their account, and it also defines what permissions (scopes) your integration needs in the user's HubSpot account.

There is an overview on how to use the HubSpot developer tools, but the steps to get up and running are:

  1. Get a developer account
  2. Create an application definition on your developer account
  3. You will need to modify the application that you created, and add accounting as a scope used.  If you plan to support contact syncing with HubSpot, you should also add the contacts scope.

Connecting to a HubSpot user's account

OAuth

To connect and gain access to a HubSpot user' account, your integration needs to initiate an OAuth connect request to HubSpot. See Working with OAuth for more information on how to initiate the connect to HubSpot.

User Account Details

After the OAuth connection with HubSpot is established, you need to make an additional call to the PUT /crm/v3/extensions/accounting/user-account endpoint to give us additional information about the user's account on your external accounting system.

The HubSpot accounting extension will make a number of webhook calls to your configured endpoints as part of the accounting features.  We send the account id of the user in your accounting system as part of these webhook calls, so that you know which account in your system we are dealing with.  Because of the way OAuth works, we don't know the account id of your user when a connection is made, so you need to make an additional call to provide this information.

Call the PUT https://api.hubapi.com/crm/v3/extensions/accounting/user-account endpoint with the OAuth access token that you just received, providing us with information about the account in your system that was connected with that  Auth token. An example payload would be:

JSON
// Example account details
{
  "accountId": "acct-app-123",
  "accountName": "My Coffee Shop Accounts",
  "currencyCode": "USD"
}

You can do the basic CRUD operations using the /user-accounts API. When a customer installs/uninstalls your app, you need to call this API and let us know which accounts are available/no longer available to use.

Authentication

There are 2 ways to authenticate API requests made to HubSpot:

  1. API Key. Your developer API key is used when managing settings related to your HubSpot apps through the API (the /settings endpoints for the accounting extension)
  2. OAuth token.  You need to use an oauth token for any request made on behalf of a HubSpot user (the /callback and /user-accounts endpoints for the accounting extension).  Note that you must ask for the accounting scope when requesting an oauth token for use with the accounting extension.

Read the Authentication documentation for more information.

Webhooks

When HubSpot send a request to your server to perform an action or retrieve information, it does this in the form of a webhook call to your endpoints.  The next sections describe what the call flow is for webhook calls, and how to configure the endpoints HubSpot should call on your server

Call Flow

Below is a graph that describes the general call flow

plantuml

The steps of the call flow are as follows:

1. Receive the webhook from HubSpot

You'll start receiving API requests to your configured URLs. The body of each request will be a JSON payload. To ensure that the requests you're getting at your webhook endpoint are actually coming from HubSpot, we populate a X-HubSpot-Signature header with a SHA-256 hash of the concatenation of the client secret for your application and the request body we're sending. For more details on validating the webhook signature, view this page.

2. Acknowledge the webhook

Your server receives the webhook, and validates that the JSON payload is well formed that the request is signed correctly.  Your server should return a 200 status code if the request is valid, or 400 if it is invalid.

Note that you should acknowledge the webhook before your server actually performs the action requested.

3. Send result of action to HubSpot

When your server has completed the requested action, it will send the result to HubSpot.

Each webhook request payload sent from HubSpot contains a metadata object which contains a callbackUrl field.  You should send the result by POSTing to the callbackUrl endpoint.  Make sure to use the OAuth access token of the HubSpot user account that is linked to the accountId sent in the request.

4. HubSpot acknowledges the result

HubSpot will respond with a 200 status code if the result is received and validated, or 400 if there was an error.

Sending an error as a result to HubSpot

Sometimes, HubSpot may send a webhook request that you deem invalid (for example, HubSpot sends a request to create a customer that already exists).  In such cases, you would send the error response to the callbackUrl.

Note that all responses to the callback endpoints should contain a "@result" field, that has a value of either "OK" or "ERR', which indicates if the response represents a success or failure of the requested task.

Error Response Structure

All the callback endpoints take the same error JSON format, an object that contains 4 fields:

  • @result: (string) for an error, this will have the value "ERR"
  • message: (string) a text description of the error that occurred.
  • category: (string) the type of error. One of the following values:
    • CUSTOMER_ALREADY_EXISTS: A customer cannot be created because they already exist
    • VALIDATION_INVALID_INPUT: A text field contains an invalid character or symbol.  For example, a customer name contains a ™ symbol which you do not support.
    • LICENSE_EXPIRED: The request was rejected because the customer's trial/license on your system has expired.
    • TAX_INFO_MISSING: There was required tax information missing, and the operation cannot be completed.
    • OBJECT_ALREADY_EXISTS: The object already exists.
    • CONNECTED_ACCOUNT_ERROR: The request was rejected because there was a problem with the account (eg. the account no longer exists, doesn't have permissions to do the requested action).
    • VALIDATION_ERROR: The input failed validation (for a reason not available in one of these error categories).  For example, the request you received was invalid JSON.
    • UNEXPECTED_ERROR: Represents a server error in your system (e.g 500 http status code).
  • timestamp: (string) either an ISO 8601 representation of the datetime that the error occurred, or in epoch milliseconds.

Here is an example error response:

JSON
// Error format
{
  "@result": "ERR",
  "message": "The customer you are trying to create already exists.",
  "category": "CUSTOMER_ALREADY_EXISTS",
  "timestamp": "2020-03-31T10:15:30Z"
}

Configuring Webhook Endpoints

In order for HubSpot to know which endpoints to send webhooks to on your server, you need to configure the "settings" for your application.  These settings contain the URLs of the endpoints that HubSpot should send webhooks to; there is a different endpoint setting for each action that HubSpot makes.

Configuring app "webhook" settings

https://api.hubapi.com/crm/v3/extensions/accounting/settings/{appId}

Send a PUT request to /settings/{appId} with the required body. You can update (PUT) or view (GET) your settings at any point.

Template Urls

We display deep links to customers in the HubSpot CRM. In order to display a link correctly and quickly, we ask integrators to provide template urls.

Possible tokens are: 

JSON
ACCOUNT_NAME
ACCOUNT_ID
INVOICE_ID
CUSTOMER_ID
PRODUCT_ID

e.g. link to an invoice in your service: "https://acountingservice.com/invoice/${ACCOUNT_ID}?invoiceId=${INVOICE_ID}"

Dummy Endpoints

HubSpot required that an endpoint URL is specified for every action in the settings.  However, when you are initially integrating, you may not yet have an implementation of each endpoint.  Our advice would be to just fill in a dummy value (such as http://example.com) until you are ready to implement the endpoint.

Webhook Endpoint Implementation Order

If you want to implement your webhook endpoints one at a time, then below is the order that they should be implemented in.  This is because this is the order the endpoints will be called in when creating an invoice:

  1. searchCustomerUrl
  2. searchProductUrl
  3. searchTaxUrl
  4. getTermsUrl
  5. createCustomerUrl
  6. createInvoiceUrl

Testing your integration on HubSpot

Create a test HubSpot account

The first thing you need to do is create a test HubSpot account from within your developer account. The test account will allow you to test and use most HubSpot features, including your accounting integration.

Find out more at How do i create a test account?

Installing your app to your test account

Now that you have created a test HubSpot account, you need to install your app in that test account.  You can verify that the app has been installed on you test account by going to the connected apps page

Please read Authorizing and installing an app for more information

Create an Invoice

Invoices in HubSpot are created from deals. In HubSpot, a deal represents an ongoing transaction that a sales team is pursuing with a contact or company.  A deal can be created in the UI by selection "Sales" from the top nav, selecting "Deals" and then pressing the "Create deal" button.  Once you have created a deal, you will be automatically brought to the overview screen of the deal you just created.  On the right-hand side of this screen, you will see an "Invoices" section.  Click on "Create invoice" and enter the details of invoice you wish to create. You should be receiving webhook calls from HubSpot for the various stages of the invoice creation flow.

Webhook Endpoints

Below the the webhook endpoints that you should configure with a call to /settings/{appId}.  The JSON payload send to each webhook endpoint and the expected callback format are described.  See the endpoints tab for more specific information on individual callback endpoints.

searchCustomerUrl

A URL that specifies the endpoint where a customer search can be performed.

1) Request to your service JSON:

JSON
// searchCustomerUrl
{
  "searchRequests": [
    {
      "query": string,
      "fieldTypes": [string]
    }
  ],
  "metadata": {
    "requestId": string
  },
  "accountId": string
}

Example JSON

JSON
// example request
{
  "searchRequests": [
    {
      "query": "Amy",
      "fieldTypes": [
        "NAME", "EMAIL"
      ]
    },
    {
      "query": "Birds",
      "fieldTypes": [
        "EMAIL"
      ]
    }
  ],
  "metadata": {
    "requestId": "tests-req-id"
  },
  "accountId": "123146316464684"
}

 

2) Response to HubSpot:

    200 (no body)

 

3) Request to HubSpot callback endpoint (/callback/customer-search/{requestId}) JSON:

JSON
// request to hubspot 
{
  "@result": "OK",
  "customers": [
    {
      "id": int,
      "name": string,
      "emailAddress": string,
      "billingAddress": {
        "lineOne": Optional<string>,
        "city": Optional<string>,
        "countrySubDivisionCode": Optional<string>,
        "postalCode": Optional<string>,
        "country": Optional<string>
      }
    },
    ...
  ]
}

Example JSON

JSON
// example
{
  "@result": "OK",
  "customers": [
    {
      "id": "1",
      "name": "Amy's Bird Sanctuary",
      "emailAddress": "Birds@company.com",
      "billingAddress": {
        "lineOne": "4581 Finch St.",
        "city": "Bayshore",
        "countrySubDivisionCode": "CA",
        "postalCode": "94326",
        "country": null
      }
    },
    {
      "id": "58",
      "name": "Bobby",
      "emailAddress": "bobby@company.com"
    }
  ]
}

4) Response to your service JSON:

    200 or 400 with error info

 

searchProductUrl

A URL that specifies the endpoint where a product search can be performed.

1) Request to your service JSON:

JSON
// searchProductUrl
{
  "searchRequests": [
    {
      "query": string,
      "fieldType": string
    }
  ],
  "metadata": {
    "requestId": string
  },
  "accountId": string
}

Example JSON:

JSON
// Example request
{
  "searchRequests": [
    {
      "query": "PROD-1",
      "fieldType": "ID"
    },
    {
      "query": "Shoes",
      "fieldType": "NAME_PARTIAL"
    },
    {
      "query": "Cotton Pants",
      "fieldType": "NAME_FULL"
    }
  ],
  "metadata": {
    "requestId": "tests-req-id"
  },
  "accountId": "123146316464684"
}

2) Response to HubSpot:

    200 (no body)

3) Request to HubSpot callback endpoint (/callback/product-search/{requestId}) JSON:

JSON
// Product Search Response Structure
{
  "@result": "OK",
  "products": [
    {
      "unitPrice": {
        "amount": number,
        "taxIncluded": boolean
      },
      "taxExempt": boolean,
      "salesTaxType": {
        "code": string,
        "name": string
      },
      "name": string,
      "description": string,
      "id": string
    }
  ]
}

Example JSON:

JSON
// Product Search Response
{
  "@result": "OK",
  "products": [
    {
      "unitPrice": {
        "amount": 10.99,
        "taxIncluded": false
      },
      "taxExempt": false,
      "salesTaxType": {
        "code": "tax-1",
        "name": "Local Sales Tax"
      },
      "name": "Marketing Services",
      "description": "Website design, Online advertising and SEO.",
      "id": "PROD-1"
    },
    {
      "unitPrice": {
        "amount": 49.99,
        "taxIncluded": false
      },
      "taxExempt": false,
      "salesTaxType": {
        "code": "tax-1",
        "name": "Local Sales Tax"
      },
      "name": "Running Shoes",
      "description": "Special shoes aimed at running.",
      "id": "PROD-2"
    },
    {
      "unitPrice": {
        "amount": 20.99,
        "taxIncluded": false
      },
      "taxExempt": false,
      "salesTaxType": {
        "code": "tax-1",
        "name": "Local Sales Tax"
      },
      "name": "Cotton Pants",
      "description": "Cotton pants, a fashion favorite for a stylish look.",
      "id": "PROD-3"
    }
  ]
}

4) Response to your service JSON:

    200 or 400 with error info

 

createInvoiceUrl

A URL that specifies the endpoint where an invoices can be created.

1) Request to your service JSON:

JSON
// Invoice Create Request
{
    "invoiceCreationRequest": {
    "customerId": Optional<String>,
    "invoiceLines": [
      {
        "productId": string,
        "qty": int,
        "unitPrice": {
          "amount": double,
          "taxIncluded": boolean
        },
        "amount": double
      }
    ],
    "createDate": date
    "dueDate": date,
    "salesTermId": Optional<string>,
    "customerMessage": Optional<String>,
    "privateMessage": Optional<String>
  },
  "customerCreationRequest": Optional<{
    "name": string,
    "emailAddress": string,
    "companyName": Optional<String>,
    "billingAddress":{
      "lineOne": Optional<String>,
      "city": Optional<String>,
      "countrySubDivisionCode": Optional<String>,
      "postalCode": Optional<String>,
      "country": Optional<String>
    }
  }>,
  "accountId": string,
  "metadata": { 
    "requestId": string 
  }
}

Example JSON

JSON
// Example Request
{
  "invoiceCreationRequest": {
    "customerId": null,
    "invoiceLines": [
      {
        "productId": "PROD-3",
        "description": "Description to include in the invoice, overriding the product's default description",
        "qty": 3,
        "unitPrice": {
          "amount": 20.99,
          "taxIncluded": false
        },
        "amount": 4
      }
    ],
    "createDate": "2020-03-31T10:15:30Z",
    "dueDate": "2020-04-30T10:15:30Z",
    "salesTermId": "net-30",
    "customerMessage": "Message included on the invoice",
    "privateMessage": "Note attached to the invoice that only the accounting system user can see"
  },
  "customerCreationRequest": {
    "name": "Amy's Bird Sanctuary",
    "emailAddress": "Birds@birds.com",
    "companyName": null,
    "billingAddress": {
      "lineOne": null,
      "city": null,
      "countrySubDivisionCode": null,
      "postalCode": null,
      "country": null
    }
  },
  "metadata": {
    "requestId": "test-req-id"
  },
  "accountId": "123146316464684"
}

2) Response to HubSpot:

    200 (no body)

3) Request to HubSpot callback endpoint (/callback/invoice-create/{requestId}) JSON:

JSON
// Invoice Create Response
{
  "@result": "OK",
  "id": string
}

4) Response to your service JSON:

    200 or 400 with error info

 

getTermsUrl

A URL that specifies the endpoint where payment terms can be retrieved.

1) Request to your service JSON:

JSON
// Terms Request
{
  "accountId" : string,
  "metadata": { 
    "requestId": string 
  }
}

2) Response to HubSpot:

    200 (no body)

3) Request to HubSpot callback endpoint (/callback/terms/{requestId}) JSON:

JSON
// Terms Response
{
  "@result": "OK",
  "terms": [
    {
      "name": string,
      "id": string,
      "dueDays": integer
    }
  ]
}

Example JSON

JSON
//example JSON
{
  "@result": "OK",
  "terms": [
    {
      "name": "Net 30",
      "id": "net-30",
      "dueDays": 30
    }
  ]
}

 

4) Response to your service JSON:

    200 or 400 with error info

 

createCustomerUrl

A URL that specifies the endpoint where a new customer can be created.

1) Request to your service JSON:

JSON
// Customer Create Request
{
  "customerCreationRequest": {
    "name": string,
    "emailAddress": string,
    "companyName": Optional<String>,
    "billingAddress":{
      "lineOne": Optional<String>,
      "city": Optional<String>,
      "countrySubDivisionCode": Optional<String>,
      "postalCode": Optional<String>,
      "country": Optional<String>
    }
  },
  "accountId": string,
  "metadata": { 
    "requestId": string 
  }
}

Example JSON

JSON
// Example Request
{
  "customerCreationRequest": {
    "name": "Amy's Bird Sanctuary",
    "emailAddress": "Birds@birds.com",
    "companyName": "HubSpot",
    "billingAddress": {
      "lineOne": "25 First Street",
      "city": "Cambridge",
      "countrySubDivisionCode": "MA",
      "postalCode": "02141",
      "country": "United States"
    }
  },
  "metadata": {
    "requestId": "test-req-id"
  },
  "accountId": "123146316464684"
}

2) Response to HubSpot:

    200 (no body)

3) Request to HubSpot callback endpoint (/callback/customer-create/{requestId}) JSON:

JSON
// Customer Create Response
{
  "@result": "OK",
  "id": string
}

Example JSON

JSON
// Example Response
{
  "@result": "OK",
  "id": "new-cust-123"
}

4) Response to your service JSON:

    200 or 400 with error info

 

getInvoiceUrl

A URL that specifies the endpoint where invoices can be retrieved.

1) Request to your service JSON:

JSON
// Get Invoices Request
{
  "invoiceIds": List<string>
  "accountId": string,
  "metadata": { 
    "requestId": string 
  }
}

Example JSON

JSON
// Example Request
{
  "invoiceIds": [
    "inv-1",
    "inv-2"
  ],
  "metadata": {
    "requestId": "test-req-id"
  },
  "accountId": "123146316464684"
}

2) Response to HubSpot:

    200 (no body)

3) Request to HubSpot callback endpoint (/callback/invoices/{requestId}) JSON:

JSON
// Customer Create Response
{
  "@result": "OK",
  "invoices": [
    {
      "invoiceId": string,
      "invoiceNumber": string,
      "currency": string,
      "amountDue": number,
      "balance": number,
      "dueDate": string,
      "customerId": string,
      "customerName": string,
      "invoiceLink": string,
      "status": string
    }
  ]
}

Example JSON

JSON
// Example Response
{
  "@result": "OK",
  "invoices": [
    {
      "invoiceId": "inv-1",
      "invoiceNumber": "INV-123",
      "currency": "USD",
      "amountDue": 100.5,
      "balance": 50,
      "dueDate": "2020-03-31",
      "customerId": "cust-123",
      "customerName": "John Smith",
      "invoiceLink": "https://myapp.com/invoices/1243a2",
      "status": "OVERDUE"
    }
  ]
}

4) Response to your service JSON:

    200 or 400 with error info

 

getInvoicePdfUrl

A URL that specifies the endpoint where an invoice PDF can be retrieved.

1) Request to your service JSON:

JSON
// Example Request
{
  "invoiceId": "inv-1",
  "metadata": {
    "requestId": "test-req-id"
  },
  "accountId": "123146316464684"
}

2) Response to HubSpot:

    200 (no body)

3) Request to HubSpot callback endpoint (/callback/invoice-pdf/{requestId}) JSON:

JSON
// Response Structure
{
  "@result": "OK",
  "invoice": string (format: byte)
}

The invoice field is a BASE64 encoded string of the bytes of the invoice PDF.

Example JSON:

JSON
// Example Response
{
  "@result": "OK",
  "invoice": "U3dhZ2dlciByb2Nrcw=="
}

4) Response to your service JSON:

    200 or 400 with error info

 

searchTaxUrl

A URL that specifies the endpoint where a tax search can be performed.

1) Request to your service JSON:

JSON
// Search Taxes Request
{
  "searchRequest": {
    "fieldType": "NAME_PARTIAL",
    "query": string,
  },
  "accountId": string,
  "metadata": { 
    "requestId": string 
  }
}

Example JSON:

JSON
// Search Taxes Request
{
  "searchRequest": {
    "fieldType": "NAME_PARTIAL",
    "query": "VAT",
  },
  "accountId": "123146316464684",
  "metadata": { 
    "requestId": "test-req-id" 
  }
}

2) Response to HubSpot:

    200 (no body)

3) Request to HubSpot callback endpoint (/callback/tax-search/{requestId}) JSON:

JSON
// Response structure
{
  "@result": "OK",
  "taxes": [
    {
      "code": string,
      "percentage": number,
      "name": string
    }
  ]
}

Example JSON:

JSON
// Example response
{
  "@result": "OK",
  "taxes": [
    {
      "code": "tax-1",
      "percentage": 13.5,
      "name": "Reduced VAT Rate"
    }
  ]
}

4) Response to your service JSON:

    200 or 400 with error info

 

searchInvoiceUrl

A URL that specifies the endpoint where an invoice search can be performed.

1) Request to your service JSON:

JSON
// Search Taxes Request
{
  "request": {
    "queryType": Optional<{
      "fieldType": string (INVOICE_NUMBER|CUSTOMER_NAME),
      "queryValues": List<string>,
    }>,
    "orderBy": string (DUE_DATE),
    "orderDirection": string (ASC|DESC),
    "pageNumber": Optional<integer>,
    "pageSize": Optional<integer>
  },
  "accountId": string,
  "metadata": { 
    "requestId": string 
  }
}

Example JSON:

JSON
// Example request
{
  "request": {
    "queryType": {
      "fieldType": "CUSTOMER_NAME",
      "queryValues": [
        "Amy"
      ],
    },
    "orderBy": "DUE_DATE",
    "orderDirection": "DESC",
    "pageNumber": 1,
    "pageSize": 20
  },
  "accountId": "123146316464684",
  "metadata": { 
    "requestId": "test-req-id" 
  }
}

2) Response to HubSpot:

    200 (no body)

3) Request to HubSpot callback endpoint (/callback/invoice-search/{requestId}) JSON:

JSON
// Search Invoice Response
{
  "@result": "OK",
  "invoices": [
    {
      "invoiceId": string,
      "invoiceNumber": string,
      "currency": string,
      "amountDue": number,
      "balance": number,
      "dueDate": string,
      "customerId": string,
      "customerName": string,
      "invoiceLink": string,
      "status": string
    }
  ]
}

Example JSON:

JSON
// Example response
{
  "@result": "OK",
  "invoices": [
    {
      "invoiceId": "inv-1",
      "invoiceNumber": "INV-123",
      "currency": "USD",
      "amountDue": 100.5,
      "balance": 50,
      "dueDate": "2020-03-31",
      "customerId": "cust-123",
      "customerName": "John Smith",
      "invoiceLink": "https://myapp.com/invoices/1243a2",
      "status": "OVERDUE"
    }
  ]
}

4) Response to your service JSON:

    200 or 400 with error info

 

exchangeRateUrl

A URL that specifies the endpoint where exchange rates can be queried.

1) Request to your service JSON:

JSON
// Exchange Rate Request
{
  "sourceCurrencyCode": string,
  "targetCurrencyCode": string,
  "accountId": string,
  "metadata": { 
    "requestId": string 
  }
}

Example JSON:

JSON
// Example request
{
  "sourceCurrencyCode": "USD",
  "targetCurrencyCode": "EUR",
  "accountId": "123146316464684",
  "metadata": { 
    "requestId": "test-req-id" 
  }
}

2) Response to HubSpot:

    200 (no body)

3) Request to HubSpot callback endpoint (/callback/exchange-rate/{requestId}) JSON:

JSON
// Exchange Rate Response
{
  "@result": "OK",
  "sourceCurrencyCode": string,
  "targetCurrencyCode": string,
  "exchangeRate": number,
}

Example JSON:

JSON
// Example response
{
  "@result": "OK",
  "sourceCurrencyCode": "USD",
  "targetCurrencyCode": "EUR",
  "exchangeRate": 0.910847,
}

4) Response to your service JSON:

    200 or 400 with error info