From e93182d42a45dc7bd5ef9ceb25434358f746f1b7 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Mon, 1 Sep 2025 11:15:39 +0300 Subject: [PATCH 01/16] Adapt New Interface to call Hubspot --- lib/hubspot/manage/client.ex | 273 ++++++++++++++++++++++++++++++++--- 1 file changed, 252 insertions(+), 21 deletions(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index 4bea59b..a5ac09d 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -7,13 +7,50 @@ defmodule Hubspot.Manage.Client do alias Hubspot.Common.API alias Hubspot.Auth.Manage.Token + @primary_standard_objects_ids %{ + "0-1" => "contact", + "0-2" => "company", + "0-3" => "deal", + "0-5" => "ticket" + } + + @primary_standard_objects_ids_map %{ + "contact" => "0-1", + "company" => "0-2", + "deal" => "0-3", + "ticket" => "0-5" + } + + # This needs to build an API function working with standard object and custom object ones + @standard_objects_types [ + :contact, + :company, + :deal, + :ticket + ] + + @plural_objects_types [ + "contacts", + "companies", + "deals", + "tickets" + ] + + @standard_objects_types_map %{ + contact: "contacts", + company: "companies", + deal: "deals", + ticket: "tickets" + } + @type standard_objects :: unquote(Enum.reduce(@standard_objects_types, &{:|, [], [&1, &2]})) + @doc """ To get from Hubspot side the metadata information about some property, like the fieldType, etc .. """ - @spec get_custom_property_metadata(String.t(), String.t(), :contact | :company, String.t()) :: + @spec get_custom_property_metadata(String.t(), String.t(), standard_objects, String.t()) :: {:ok, map()} | {:error, map()} def get_custom_property_metadata(client_code, refresh_token, object_type, property_name) - when object_type in [:contact, :company] do + when object_type in @standard_objects_types do with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), {:ok, %{status: status, body: body}} <- API.request( @@ -39,10 +76,10 @@ defmodule Hubspot.Manage.Client do @doc """ list all client's object(contact, company) properties """ - @spec list_custom_properties(String.t(), String.t(), :contact | :company) :: + @spec list_custom_properties(String.t(), String.t(), standard_objects) :: {:ok, list()} | {:error, map()} def list_custom_properties(client_code, refresh_token, object_type) - when object_type in [:contact, :company] do + when object_type in @standard_objects_types do with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), {:ok, %{status: status, body: body}} <- API.request( @@ -149,15 +186,6 @@ defmodule Hubspot.Manage.Client do end end - defp to_property(property), - do: %{ - id: property["name"], - title: property["label"], - hubspot_defined: property["hubspotDefined"], - fieldType: property["fieldType"], - type: property["type"] - } - @doc """ list all client's object(contact, company) properties """ @@ -221,7 +249,7 @@ defmodule Hubspot.Manage.Client do @spec get_objects_by_property_values( String.t(), String.t(), - :contact | :company, + standard_objects, String.t(), list(), String.t(), @@ -239,7 +267,7 @@ defmodule Hubspot.Manage.Client do property_values, limit \\ 10 ) - when object_type in [:contact, :company] do + when object_type in @standard_objects_types do client_code |> Token.get_client_access_token(refresh_token) |> case do @@ -292,7 +320,7 @@ defmodule Hubspot.Manage.Client do @spec get_object_by_property( String.t(), String.t(), - :contact | :company, + standard_objects, String.t(), String.t() ) :: @@ -305,7 +333,7 @@ defmodule Hubspot.Manage.Client do property_value, properties \\ [] ) - when object_type in [:contact, :company] do + when object_type in @standard_objects_types do client_code |> Token.get_client_access_token(refresh_token) |> case do @@ -350,14 +378,14 @@ defmodule Hubspot.Manage.Client do @spec list_objects( String.t(), String.t(), - :contact | :company, + standard_objects, String.t(), String.t() | nil, list() ) :: {:ok, map()} | {:error, map()} def list_objects(client_code, refresh_token, object_type, page_size, after_token, properties) - when object_type in [:contact, :company] do + when object_type in @standard_objects_types do query_params = to_query_params_string( limit: page_size, @@ -387,8 +415,143 @@ defmodule Hubspot.Manage.Client do end end - defp to_object_type(:contact), do: "contacts" - defp to_object_type(:company), do: "companies" + @spec discovery_custom_objects(String.t(), String.t()) :: {:ok, map()} | {:error, map()} + def discovery_custom_objects(client_code, refresh_token) do + with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), + {:ok, %{status: status, body: body}} <- + API.request( + :get, + "crm/v3/schemas", + nil, + [ + {"Content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ) do + {:ok, %{status: status, body: body}} + else + {:not_found, reason} -> + {:error, reason} + + error -> + error + end + end + + @spec discovery_objects(String.t(), String.t()) :: {:ok, map()} | {:error, map()} + def discovery_objects(client_code, refresh_token) do + with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), + {:ok, %{status: status, body: body}} <- + API.request( + :get, + "crm/v3/schemas", + nil, + [ + {"Content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ) do + {:ok, + %{ + status: status, + body: Enum.map(body["results"], &to_custom_object/1) ++ bind_standard_objects() + }} + else + {:not_found, reason} -> + {:error, reason} + + error -> + error + end + end + + @spec get_object_properties(String.t(), String.t(), String.t()) :: + {:ok, map()} | {:error, map()} + def get_object_properties(client_code, refresh_token, object_name) do + with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), + {:ok, %{status: status, body: body}} <- + API.request( + :get, + "crm/v3/schemas/#{maybe_alter_object_name(object_name)}", + nil, + [ + {"Content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ) do + {:ok, %{status: status, body: Enum.map(body["properties"], &to_property/1)}} + else + {:not_found, reason} -> + {:error, reason} + + error -> + error + end + end + + @spec get_related_custom_events(String.t(), String.t(), keyword()) :: + {:ok, map()} | {:error, map()} + def get_related_custom_events(client_code, refresh_token, opts \\ []) do + search_term = Keyword.get(opts, :search_term, "") + include_properties = Keyword.get(opts, :include_properties, false) + + with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), + {:ok, %{status: status, body: body}} <- + API.request( + :get, + "events/v3/event-definitions?searchString=#{search_term}&includeProperties=#{include_properties}", + nil, + [ + {"Content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ), + custom_event_names_mapping <- + maybe_build_custom_event_names_mapping(client_code, refresh_token, opts) do + {:ok, + %{ + status: status, + body: + Enum.map(body["results"], fn event -> + event + |> maybe_add_standard_object_name() + |> maybe_add_custom_event_name(custom_event_names_mapping) + end) + }} + end + end + + defp bind_standard_objects(), + do: + @standard_objects_types + |> Enum.map(fn object_name -> + %{ + fully_qualified_name: @standard_objects_types_map[object_name], + singular_name: to_string(object_name), + plural_name: @standard_objects_types_map[object_name], + is_standard_object: true, + primary_object_id: @primary_standard_objects_ids_map[to_string(object_name)] + } + end) + + defp to_custom_object(object) do + %{ + fully_qualified_name: object["fullyQualifiedName"], + singular_name: object["labels"]["singular"], + plural_name: object["labels"]["plural"], + primary_object_id: object["objectTypeId"], + type: "custom_object" + } + end + + defp to_object_type(object_type) when object_type in @standard_objects_types, + do: to_string(object_type) + + defp to_object_type(object_type), do: raise("Invalid object type: #{inspect(object_type)}") defp to_properties_string(properties), do: Enum.join(properties, ",") @@ -397,4 +560,72 @@ defmodule Hubspot.Manage.Client do |> Enum.reject(fn {_key, val} -> is_nil(val) end) |> Enum.map_join("&", fn {key, val} -> "#{key}=#{val}" end) end + + defp to_property(property), + do: %{ + id: property["name"], + title: property["label"], + hubspot_defined: property["hubspotDefined"] || false, + fieldType: property["fieldType"], + type: property["type"] + } + + defp maybe_alter_object_name(object_name) when object_name in @plural_objects_types, + do: object_name + + defp maybe_alter_object_name(object_name), do: "p_#{object_name}" + + defp maybe_add_standard_object_name(event) do + case Map.get(event, "primaryObjectId") do + nil -> + event + + object_id -> + case Map.get(@primary_standard_objects_ids, object_id) do + nil -> + event + + object_name -> + event + |> Map.put("objectName", String.capitalize(object_name)) + |> Map.put("isStandardObject", true) + end + end + end + + defp maybe_add_custom_event_name(event, objects_ids_names_mapping) do + case Map.get(objects_ids_names_mapping, event["primaryObjectId"]) do + nil -> + event + + mapped_object_name -> + event + |> Map.put("objectName", String.capitalize(mapped_object_name)) + |> Map.put("isStandardObject", false) + end + end + + defp maybe_build_custom_event_names_mapping(client_code, refresh_token, opts) do + case Keyword.get(opts, :custom_event_names_mapping) do + custom_event_names_mapping + when is_map(custom_event_names_mapping) and map_size(custom_event_names_mapping) > 0 -> + custom_event_names_mapping + + _ -> + build_custom_event_names_mapping(client_code, refresh_token) + end + end + + defp build_custom_event_names_mapping(client_code, refresh_token) do + with {:ok, %{status: 200, body: %{"results" => custom_objects} = _body}} <- + discovery_custom_objects(client_code, refresh_token) do + custom_objects + |> Enum.reduce(%{}, fn object, acc -> + Map.put(acc, object["objectTypeId"], object["labels"]["plural"]) + end) + else + _error -> + %{} + end + end end From 95f55ba020a991e1582844dd50fc037818b4bc53 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 2 Sep 2025 15:50:19 +0300 Subject: [PATCH 02/16] Fix credo suggest --- lib/hubspot/manage/client.ex | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index a5ac09d..ce3d504 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -617,13 +617,13 @@ defmodule Hubspot.Manage.Client do end defp build_custom_event_names_mapping(client_code, refresh_token) do - with {:ok, %{status: 200, body: %{"results" => custom_objects} = _body}} <- - discovery_custom_objects(client_code, refresh_token) do - custom_objects - |> Enum.reduce(%{}, fn object, acc -> - Map.put(acc, object["objectTypeId"], object["labels"]["plural"]) - end) - else + case discovery_custom_objects(client_code, refresh_token) do + {:ok, %{status: 200, body: %{"results" => custom_objects} = _body}} -> + custom_objects + |> Enum.reduce(%{}, fn object, acc -> + Map.put(acc, object["objectTypeId"], object["labels"]["plural"]) + end) + _error -> %{} end From d8e1daffcbaa135a08d9d8cc44da8b0438dbe0d6 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 2 Sep 2025 19:19:24 +0300 Subject: [PATCH 03/16] unify the responses --- lib/hubspot/manage/client.ex | 112 +++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 31 deletions(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index ce3d504..2fea318 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -456,7 +456,7 @@ defmodule Hubspot.Manage.Client do {:ok, %{ status: status, - body: Enum.map(body["results"], &to_custom_object/1) ++ bind_standard_objects() + body: Enum.map(body["results"], &to_object(&1, :custom_object)) ++ get_standard_objects() }} else {:not_found, reason} -> @@ -511,40 +511,43 @@ defmodule Hubspot.Manage.Client do ] ), custom_event_names_mapping <- - maybe_build_custom_event_names_mapping(client_code, refresh_token, opts) do + maybe_fetch_related_object_details(client_code, refresh_token, opts) do {:ok, %{ status: status, body: Enum.map(body["results"], fn event -> event - |> maybe_add_standard_object_name() - |> maybe_add_custom_event_name(custom_event_names_mapping) + |> maybe_add_standard_object_details_to_custom_event() + |> maybe_add_custom_object_details_to_custom_event(custom_event_names_mapping) + |> to_custom_event() end) }} end end - defp bind_standard_objects(), - do: - @standard_objects_types - |> Enum.map(fn object_name -> - %{ - fully_qualified_name: @standard_objects_types_map[object_name], - singular_name: to_string(object_name), - plural_name: @standard_objects_types_map[object_name], - is_standard_object: true, - primary_object_id: @primary_standard_objects_ids_map[to_string(object_name)] - } - end) + defp get_standard_objects(), + do: Enum.map(@standard_objects_types, &to_object(&1, :standard_object)) - defp to_custom_object(object) do + defp to_object(object, :custom_object) do %{ fully_qualified_name: object["fullyQualifiedName"], singular_name: object["labels"]["singular"], plural_name: object["labels"]["plural"], primary_object_id: object["objectTypeId"], - type: "custom_object" + is_standard_object: false, + is_custom_object: true + } + end + + defp to_object(object_name, :standard_object) do + %{ + fully_qualified_name: @standard_objects_types_map[object_name], + singular_name: to_string(object_name), + plural_name: @standard_objects_types_map[object_name], + primary_object_id: @primary_standard_objects_ids_map[to_string(object_name)], + is_standard_object: true, + is_custom_object: false } end @@ -565,8 +568,7 @@ defmodule Hubspot.Manage.Client do do: %{ id: property["name"], title: property["label"], - hubspot_defined: property["hubspotDefined"] || false, - fieldType: property["fieldType"], + is_custom_property: property["hubspotDefined"] || false, type: property["type"] } @@ -575,7 +577,7 @@ defmodule Hubspot.Manage.Client do defp maybe_alter_object_name(object_name), do: "p_#{object_name}" - defp maybe_add_standard_object_name(event) do + defp maybe_add_standard_object_details_to_custom_event(event) do case Map.get(event, "primaryObjectId") do nil -> event @@ -587,45 +589,93 @@ defmodule Hubspot.Manage.Client do object_name -> event - |> Map.put("objectName", String.capitalize(object_name)) - |> Map.put("isStandardObject", true) + |> Map.put( + "related_object_fully_qualified_name", + String.capitalize(@standard_objects_types_map[String.to_atom(object_name)]) + ) + |> Map.put("related_object_name", String.capitalize(object_name)) + |> Map.put("related_object_primary_object_id", object_id) + |> Map.put("related_object_singular_name", object_name) + |> Map.put( + "related_object_plural_name", + @standard_objects_types_map[String.to_atom(object_name)] + ) + |> Map.put("is_related_standard_object", true) + |> Map.put("is_related_custom_object", false) end end end - defp maybe_add_custom_event_name(event, objects_ids_names_mapping) do + defp maybe_add_custom_object_details_to_custom_event(event, objects_ids_names_mapping) do case Map.get(objects_ids_names_mapping, event["primaryObjectId"]) do nil -> event - mapped_object_name -> + mapped_object_name when is_map(mapped_object_name) -> event - |> Map.put("objectName", String.capitalize(mapped_object_name)) - |> Map.put("isStandardObject", false) + |> Map.put( + "related_object_fully_qualified_name", + String.capitalize(mapped_object_name["fully_qualified_name"]) + ) + |> Map.put("related_object_name", mapped_object_name["plural_name"]) + |> Map.put( + "related_object_primary_object_id", + mapped_object_name["object_primary_object_id"] + ) + |> Map.put("related_object_singular_name", mapped_object_name["singular_name"]) + |> Map.put("is_related_standard_object", mapped_object_name["is_standard_object"]) + |> Map.put("is_related_custom_object", mapped_object_name["is_custom_object"]) end end - defp maybe_build_custom_event_names_mapping(client_code, refresh_token, opts) do + defp maybe_fetch_related_object_details(client_code, refresh_token, opts) do case Keyword.get(opts, :custom_event_names_mapping) do custom_event_names_mapping when is_map(custom_event_names_mapping) and map_size(custom_event_names_mapping) > 0 -> custom_event_names_mapping _ -> - build_custom_event_names_mapping(client_code, refresh_token) + fetch_related_object_details(client_code, refresh_token) end end - defp build_custom_event_names_mapping(client_code, refresh_token) do + defp fetch_related_object_details(client_code, refresh_token) do case discovery_custom_objects(client_code, refresh_token) do {:ok, %{status: 200, body: %{"results" => custom_objects} = _body}} -> custom_objects |> Enum.reduce(%{}, fn object, acc -> - Map.put(acc, object["objectTypeId"], object["labels"]["plural"]) + Map.put(acc, object["objectTypeId"], %{ + "fully_qualified_name" => object["fullyQualifiedName"], + "plural_name" => object["labels"]["plural"], + "singular_name" => object["labels"]["singular"], + "is_standard_object" => false, + "is_custom_object" => true + }) end) _error -> %{} end end + + defp to_custom_event(event) do + %{ + id: event["id"], + description: event["description"], + fully_qualified_name: event["fullyQualifiedName"], + is_related_custom_object: event["is_related_custom_object"], + is_related_standard_object: event["is_related_standard_object"], + labels: event["labels"], + name: event["name"], + object_type_id: event["objectTypeId"], + primary_object: event["primaryObject"], + primary_object_id: event["primaryObjectId"], + properties: event["properties"], + related_object_fully_qualified_name: event["related_object_fully_qualified_name"], + related_object_name: event["related_object_name"], + related_object_primary_object_id: event["related_object_primary_object_id"], + related_object_singular_name: event["related_object_singular_name"], + tracking_type: event["trackingType"] + } + end end From 4a5a75942118764f95dcb5245a971d4dc72d4d03 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 9 Sep 2025 11:44:48 +0300 Subject: [PATCH 04/16] Refactors, and adapt create custom events, and if can use the custom events --- lib/hubspot/auth/management/token.ex | 25 +- lib/hubspot/manage/client.ex | 344 +++++++++++++-------------- 2 files changed, 189 insertions(+), 180 deletions(-) diff --git a/lib/hubspot/auth/management/token.ex b/lib/hubspot/auth/management/token.ex index 06221f0..c0d1b41 100644 --- a/lib/hubspot/auth/management/token.ex +++ b/lib/hubspot/auth/management/token.ex @@ -6,8 +6,24 @@ defmodule Hubspot.Auth.Manage.Token do @ttl :timer.seconds(1_800) + @spec get_client_scopes(any, any) :: {:not_found, any} | {:ok, any} | {:commit, any} + def get_client_scopes(client_code, refresh_token) do + Cachex.fetch(:hubspot_cache, client_code <> "_scopes", fn _key -> + case generate_new_access_token(refresh_token) do + {:ok, %{"scopes" => scopes}} -> + {:commit, scopes} + + error -> + {:ignore, + "Failed to generate an access token for Hubspot OAuth management API for client with code #{client_code} with error #{inspect(error)}"} + end + end) + |> maybe_set_cache(client_code) + |> normalize_cache_fetch() + end + # Hubspot clients are identified by their code(returned after initial authentication flow) - @spec get_client_access_token(any, any) :: {:not_found, any} | {:ok, any} + @spec get_client_access_token(any, any) :: {:not_found, any} | {:ok, any} | {:commit, any} def get_client_access_token(client_code, refresh_token) do Cachex.fetch(:hubspot_cache, client_code, fn key -> Logger.info( @@ -15,9 +31,8 @@ defmodule Hubspot.Auth.Manage.Token do ) case generate_new_access_token(refresh_token) do - {:ok, %{"access_token" => access_token}} -> - Cachex.expire(:hubspot_cache, client_code, @ttl) - + {:ok, %{"access_token" => access_token, "scopes" => scopes}} -> + Cachex.put(:hubspot_cache, client_code <> "_scopes", scopes) {:commit, access_token} error -> @@ -76,7 +91,7 @@ defmodule Hubspot.Auth.Manage.Token do # defp maybe_set_cache({:commit, _} = response, client_code) do Cachex.expire(:hubspot_cache, client_code, @ttl) - + Cachex.expire(:hubspot_cache, client_code <> "_scopes", @ttl) response end diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index 2fea318..59fa66f 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -7,41 +7,66 @@ defmodule Hubspot.Manage.Client do alias Hubspot.Common.API alias Hubspot.Auth.Manage.Token + @custom_events_write_scope "behavioral_events.event_definitions.read_write" + @primary_standard_objects_ids %{ "0-1" => "contact", "0-2" => "company", "0-3" => "deal", - "0-5" => "ticket" + "0-5" => "ticket", + "0-162" => "service", + "0-53" => "invoice", + "0-7" => "product", + "0-14" => "quote", + "0-69" => "subscription", + "0-123" => "order", + "0-142" => "cart" } - @primary_standard_objects_ids_map %{ - "contact" => "0-1", - "company" => "0-2", - "deal" => "0-3", - "ticket" => "0-5" - } + @primary_standard_objects_ids_map @primary_standard_objects_ids + |> Enum.map(fn {k, v} -> {v, k} end) + |> Enum.into(%{}) # This needs to build an API function working with standard object and custom object ones - @standard_objects_types [ - :contact, - :company, - :deal, - :ticket - ] + @standard_objects_types @primary_standard_objects_ids + |> Enum.map(fn {_k, v} -> String.to_existing_atom(v) end) + |> Enum.into([]) - @plural_objects_types [ + @eventable_standard_objects_types [ "contacts", "companies", "deals", "tickets" ] + @plural_standard_objects_types [ + "contacts", + "companies", + "deals", + "tickets", + "services", + "invoices", + "products", + "quotes", + "subscriptions", + "orders", + "carts" + ] + @standard_objects_types_map %{ contact: "contacts", company: "companies", deal: "deals", - ticket: "tickets" + ticket: "tickets", + service: "services", + invoice: "invoices", + product: "products", + quote: "quotes", + subscription: "subscriptions", + order: "orders", + cart: "carts" } + @type standard_objects :: unquote(Enum.reduce(@standard_objects_types, &{:|, [], [&1, &2]})) @doc """ @@ -186,6 +211,119 @@ defmodule Hubspot.Manage.Client do end end + @spec allowed_to_use_custom_events?(String.t(), String.t()) :: + {:ok, boolean()} | {:error, map()} + def allowed_to_use_custom_events?(client_code, refresh_token) do + client_code + |> Token.get_client_scopes(refresh_token) + |> case do + {:ok, scope} -> + if @custom_events_write_scope in scope do + {:ok, true} + else + {:ok, false} + end + + {:not_found, reason} -> + {:error, reason} + end + end + + @spec get_custom_event(String.t(), String.t(), String.t()) :: {:ok, map()} | {:error, map()} + def get_custom_event(client_code, refresh_token, custom_event_name) do + client_code + |> Token.get_client_access_token(refresh_token) + |> case do + {:ok, token} -> + API.request(:get, "/events/v3/event-definitions/#{custom_event_name}", nil, [ + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ]) + + {:error, reason} -> + {:error, reason} + end + end + + @spec send_custom_event( + String.t(), + String.t(), + :object_id | :email, + String.t(), + map(), + String.t() + ) :: + {:ok, map()} | {:error, map()} + def send_custom_event( + client_code, + refresh_token, + :object_id, + custom_event_name, + params, + object_id + ) do + client_code + |> Token.get_client_access_token(refresh_token) + |> case do + {:ok, token} -> + API.request( + :post, + "/events/v3/send", + Jason.encode!(%{ + eventName: custom_event_name, + objectId: object_id, + occurredAt: Map.get(params, "occurred_at", DateTime.now!("Etc/UTC")), + properties: %{ + event_type: Map.get(params, "event_type", ""), + event_id: Map.get(params, "event_id", ""), + event_name: Map.get(params, "event_name", ""), + event_title: Map.get(params, "event_title", ""), + event_platform: Map.get(params, "event_platform", ""), + hostname: Map.get(params, "hostname", ""), + pathname: Map.get(params, "pathname", "") + } + }), + [ + {"Content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ) + + {:not_found, reason} -> + {:error, reason} + end + end + + @spec define_custom_event(String.t(), String.t(), map()) :: {:ok, map()} | {:error, map()} + def define_custom_event(client_code, refresh_token, event_body) do + client_code + |> Token.get_client_access_token(refresh_token) + |> case do + {:ok, token} -> + API.request( + :post, + "/events/v3/event-definitions", + Jason.encode!(%{ + label: event_body[:label], + name: event_body[:name], + description: event_body[:description], + primaryObject: event_body[:primary_object], + includeDefaultProperties: event_body[:include_default_properties] || true, + propertyDefinitions: event_body[:property_definitions] + }), + [ + {"Content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ) + + {:error, reason} -> + {:error, reason} + end + end + @doc """ list all client's object(contact, company) properties """ @@ -415,30 +553,6 @@ defmodule Hubspot.Manage.Client do end end - @spec discovery_custom_objects(String.t(), String.t()) :: {:ok, map()} | {:error, map()} - def discovery_custom_objects(client_code, refresh_token) do - with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), - {:ok, %{status: status, body: body}} <- - API.request( - :get, - "crm/v3/schemas", - nil, - [ - {"Content-type", "application/json"}, - {"authorization", "Bearer #{token}"}, - {"accept", "application/json"} - ] - ) do - {:ok, %{status: status, body: body}} - else - {:not_found, reason} -> - {:error, reason} - - error -> - error - end - end - @spec discovery_objects(String.t(), String.t()) :: {:ok, map()} | {:error, map()} def discovery_objects(client_code, refresh_token) do with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), @@ -492,40 +606,6 @@ defmodule Hubspot.Manage.Client do end end - @spec get_related_custom_events(String.t(), String.t(), keyword()) :: - {:ok, map()} | {:error, map()} - def get_related_custom_events(client_code, refresh_token, opts \\ []) do - search_term = Keyword.get(opts, :search_term, "") - include_properties = Keyword.get(opts, :include_properties, false) - - with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), - {:ok, %{status: status, body: body}} <- - API.request( - :get, - "events/v3/event-definitions?searchString=#{search_term}&includeProperties=#{include_properties}", - nil, - [ - {"Content-type", "application/json"}, - {"authorization", "Bearer #{token}"}, - {"accept", "application/json"} - ] - ), - custom_event_names_mapping <- - maybe_fetch_related_object_details(client_code, refresh_token, opts) do - {:ok, - %{ - status: status, - body: - Enum.map(body["results"], fn event -> - event - |> maybe_add_standard_object_details_to_custom_event() - |> maybe_add_custom_object_details_to_custom_event(custom_event_names_mapping) - |> to_custom_event() - end) - }} - end - end - defp get_standard_objects(), do: Enum.map(@standard_objects_types, &to_object(&1, :standard_object)) @@ -536,7 +616,9 @@ defmodule Hubspot.Manage.Client do plural_name: object["labels"]["plural"], primary_object_id: object["objectTypeId"], is_standard_object: false, - is_custom_object: true + is_custom_object: true, + requires_custom_events: true, + eventable: eventable?(object["labels"]["plural"], :custom_object) } end @@ -547,7 +629,13 @@ defmodule Hubspot.Manage.Client do plural_name: @standard_objects_types_map[object_name], primary_object_id: @primary_standard_objects_ids_map[to_string(object_name)], is_standard_object: true, - is_custom_object: false + is_custom_object: false, + requires_custom_events: + not Enum.member?( + @eventable_standard_objects_types, + @standard_objects_types_map[object_name] + ), + eventable: eventable?(@standard_objects_types_map[object_name], :standard_object) } end @@ -572,110 +660,16 @@ defmodule Hubspot.Manage.Client do type: property["type"] } - defp maybe_alter_object_name(object_name) when object_name in @plural_objects_types, + defp maybe_alter_object_name(object_name) when object_name in @plural_standard_objects_types, do: object_name defp maybe_alter_object_name(object_name), do: "p_#{object_name}" - defp maybe_add_standard_object_details_to_custom_event(event) do - case Map.get(event, "primaryObjectId") do - nil -> - event - - object_id -> - case Map.get(@primary_standard_objects_ids, object_id) do - nil -> - event - - object_name -> - event - |> Map.put( - "related_object_fully_qualified_name", - String.capitalize(@standard_objects_types_map[String.to_atom(object_name)]) - ) - |> Map.put("related_object_name", String.capitalize(object_name)) - |> Map.put("related_object_primary_object_id", object_id) - |> Map.put("related_object_singular_name", object_name) - |> Map.put( - "related_object_plural_name", - @standard_objects_types_map[String.to_atom(object_name)] - ) - |> Map.put("is_related_standard_object", true) - |> Map.put("is_related_custom_object", false) - end - end - end - - defp maybe_add_custom_object_details_to_custom_event(event, objects_ids_names_mapping) do - case Map.get(objects_ids_names_mapping, event["primaryObjectId"]) do - nil -> - event - - mapped_object_name when is_map(mapped_object_name) -> - event - |> Map.put( - "related_object_fully_qualified_name", - String.capitalize(mapped_object_name["fully_qualified_name"]) - ) - |> Map.put("related_object_name", mapped_object_name["plural_name"]) - |> Map.put( - "related_object_primary_object_id", - mapped_object_name["object_primary_object_id"] - ) - |> Map.put("related_object_singular_name", mapped_object_name["singular_name"]) - |> Map.put("is_related_standard_object", mapped_object_name["is_standard_object"]) - |> Map.put("is_related_custom_object", mapped_object_name["is_custom_object"]) - end - end - - defp maybe_fetch_related_object_details(client_code, refresh_token, opts) do - case Keyword.get(opts, :custom_event_names_mapping) do - custom_event_names_mapping - when is_map(custom_event_names_mapping) and map_size(custom_event_names_mapping) > 0 -> - custom_event_names_mapping + defp eventable?(_object_name, :custom_object), do: true - _ -> - fetch_related_object_details(client_code, refresh_token) - end - end - - defp fetch_related_object_details(client_code, refresh_token) do - case discovery_custom_objects(client_code, refresh_token) do - {:ok, %{status: 200, body: %{"results" => custom_objects} = _body}} -> - custom_objects - |> Enum.reduce(%{}, fn object, acc -> - Map.put(acc, object["objectTypeId"], %{ - "fully_qualified_name" => object["fullyQualifiedName"], - "plural_name" => object["labels"]["plural"], - "singular_name" => object["labels"]["singular"], - "is_standard_object" => false, - "is_custom_object" => true - }) - end) - - _error -> - %{} - end - end + defp eventable?(object_name, :standard_object) + when object_name in @eventable_standard_objects_types, + do: true - defp to_custom_event(event) do - %{ - id: event["id"], - description: event["description"], - fully_qualified_name: event["fullyQualifiedName"], - is_related_custom_object: event["is_related_custom_object"], - is_related_standard_object: event["is_related_standard_object"], - labels: event["labels"], - name: event["name"], - object_type_id: event["objectTypeId"], - primary_object: event["primaryObject"], - primary_object_id: event["primaryObjectId"], - properties: event["properties"], - related_object_fully_qualified_name: event["related_object_fully_qualified_name"], - related_object_name: event["related_object_name"], - related_object_primary_object_id: event["related_object_primary_object_id"], - related_object_singular_name: event["related_object_singular_name"], - tracking_type: event["trackingType"] - } - end + defp eventable?(_object_name, :standard_object), do: false end From f750ff2ebc00f7a496d75f12c21683ec62ceb5a0 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 9 Sep 2025 12:10:09 +0300 Subject: [PATCH 05/16] Fix dialyzer --- lib/hubspot/manage/client.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index 59fa66f..e870145 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -240,6 +240,9 @@ defmodule Hubspot.Manage.Client do {"accept", "application/json"} ]) + {:not_found, reason} -> + {:error, reason} + {:error, reason} -> {:error, reason} end @@ -319,6 +322,9 @@ defmodule Hubspot.Manage.Client do ] ) + {:not_found, reason} -> + {:error, reason} + {:error, reason} -> {:error, reason} end From 18b033c7a248442707d42e423a524439445680f0 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 9 Sep 2025 12:14:14 +0300 Subject: [PATCH 06/16] Fix dialyzer --- lib/hubspot/manage/client.ex | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index e870145..59a5d94 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -242,9 +242,6 @@ defmodule Hubspot.Manage.Client do {:not_found, reason} -> {:error, reason} - - {:error, reason} -> - {:error, reason} end end @@ -324,9 +321,6 @@ defmodule Hubspot.Manage.Client do {:not_found, reason} -> {:error, reason} - - {:error, reason} -> - {:error, reason} end end From 041e806bbb9eedcbb06467fab55bcb7b793a134e Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Mon, 22 Sep 2025 11:05:34 +0300 Subject: [PATCH 07/16] Remove Alter object name --- lib/hubspot/manage/client.ex | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index 59a5d94..e4eca01 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -588,7 +588,7 @@ defmodule Hubspot.Manage.Client do {:ok, %{status: status, body: body}} <- API.request( :get, - "crm/v3/schemas/#{maybe_alter_object_name(object_name)}", + "crm/v3/schemas/#{object_name}", nil, [ {"Content-type", "application/json"}, @@ -660,11 +660,6 @@ defmodule Hubspot.Manage.Client do type: property["type"] } - defp maybe_alter_object_name(object_name) when object_name in @plural_standard_objects_types, - do: object_name - - defp maybe_alter_object_name(object_name), do: "p_#{object_name}" - defp eventable?(_object_name, :custom_object), do: true defp eventable?(object_name, :standard_object) From fbd9e8db2ff1738aa2ef5ff38c019dac512307ca Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 30 Sep 2025 17:12:11 +0300 Subject: [PATCH 08/16] Support bach track custom event --- lib/hubspot/common/api.ex | 4 +++ lib/hubspot/manage/client.ex | 61 ++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/lib/hubspot/common/api.ex b/lib/hubspot/common/api.ex index d903949..9b29ae0 100644 --- a/lib/hubspot/common/api.ex +++ b/lib/hubspot/common/api.ex @@ -65,6 +65,10 @@ defmodule Hubspot.Common.API do defp decode_response({:error, %Mint.TransportError{reason: reason} = error}), do: {:error, %{status: nil, body: "#{reason}: #{Exception.message(error)}", headers: nil}} + defp decode_response(%Finch.Response{status: status, body: body, headers: headers} = _response) + when status >= 200 and status < 300 and body == "", + do: {:ok, %{status: status, body: body, headers: headers}} + defp decode_response(%Finch.Response{status: status, body: body, headers: headers} = _response) when status >= 200 and status < 300, do: {:ok, %{status: status, body: Jason.decode!(body), headers: headers}} diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index e4eca01..fafdaaa 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -250,7 +250,8 @@ defmodule Hubspot.Manage.Client do String.t(), :object_id | :email, String.t(), - map(), + DateTime.t(), + map() | list(), String.t() ) :: {:ok, map()} | {:error, map()} @@ -259,9 +260,11 @@ defmodule Hubspot.Manage.Client do refresh_token, :object_id, custom_event_name, - params, + occurred_at, + properties, object_id - ) do + ) + when is_map(properties) do client_code |> Token.get_client_access_token(refresh_token) |> case do @@ -272,16 +275,8 @@ defmodule Hubspot.Manage.Client do Jason.encode!(%{ eventName: custom_event_name, objectId: object_id, - occurredAt: Map.get(params, "occurred_at", DateTime.now!("Etc/UTC")), - properties: %{ - event_type: Map.get(params, "event_type", ""), - event_id: Map.get(params, "event_id", ""), - event_name: Map.get(params, "event_name", ""), - event_title: Map.get(params, "event_title", ""), - event_platform: Map.get(params, "event_platform", ""), - hostname: Map.get(params, "hostname", ""), - pathname: Map.get(params, "pathname", "") - } + occurredAt: occurred_at, + properties: properties }), [ {"Content-type", "application/json"}, @@ -295,6 +290,46 @@ defmodule Hubspot.Manage.Client do end end + def send_custom_event( + client_code, + refresh_token, + :object_id, + custom_event_name, + occurred_at, + properties, + object_id + ) + when is_list(properties) do + events = %{ + inputs: + Enum.map(properties, fn property -> + property + |> Map.put(:eventName, custom_event_name) + |> Map.put(:objectId, object_id) + |> Map.put(:occurredAt, occurred_at) + end) + } + + client_code + |> Token.get_client_access_token(refresh_token) + |> case do + {:ok, token} -> + API.request( + :post, + "/events/v3/send/batch", + Jason.encode!(events), + [ + {"Content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ) + + {:not_found, reason} -> + {:error, reason} + end + end + @spec define_custom_event(String.t(), String.t(), map()) :: {:ok, map()} | {:error, map()} def define_custom_event(client_code, refresh_token, event_body) do client_code From 000f39727f22136b4b75128eb3a41a009cb80926 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Wed, 22 Oct 2025 11:28:31 +0300 Subject: [PATCH 09/16] fix is_custom_property detected --- lib/hubspot/manage/client.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index fafdaaa..add5e71 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -691,7 +691,12 @@ defmodule Hubspot.Manage.Client do do: %{ id: property["name"], title: property["label"], - is_custom_property: property["hubspotDefined"] || false, + is_custom_property: + if property["hubspotDefined"] do + false + else + true + end, type: property["type"] } From 2ac33837bb23b7ad6cd19edb75fab8e0e2137e8a Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Sun, 2 Nov 2025 12:41:39 +0200 Subject: [PATCH 10/16] Done for sync and first identify --- lib/hubspot/manage/client.ex | 41 +++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index fafdaaa..3d0392d 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -74,8 +74,7 @@ defmodule Hubspot.Manage.Client do """ @spec get_custom_property_metadata(String.t(), String.t(), standard_objects, String.t()) :: {:ok, map()} | {:error, map()} - def get_custom_property_metadata(client_code, refresh_token, object_type, property_name) - when object_type in @standard_objects_types do + def get_custom_property_metadata(client_code, refresh_token, object_type, property_name) do with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), {:ok, %{status: status, body: body}} <- API.request( @@ -103,8 +102,7 @@ defmodule Hubspot.Manage.Client do """ @spec list_custom_properties(String.t(), String.t(), standard_objects) :: {:ok, list()} | {:error, map()} - def list_custom_properties(client_code, refresh_token, object_type) - when object_type in @standard_objects_types do + def list_custom_properties(client_code, refresh_token, object_type) do with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), {:ok, %{status: status, body: body}} <- API.request( @@ -385,6 +383,35 @@ defmodule Hubspot.Manage.Client do end end + @spec get_object_by_email(String.t(), String.t(), String.t(), String.t(), String.t()) :: + {:ok, map()} | {:error, map()} + def get_object_by_email( + client_code, + refresh_token, + object_qualified_name, + email, + properties \\ [] + ) do + client_code + |> Token.get_client_access_token(refresh_token) + |> case do + {:ok, token} -> + API.request( + :get, + "crm/v3/objects/#{object_qualified_name}/#{String.trim(email)}?idProperty=email&properties=#{to_properties_string(properties)}", + nil, + [ + {"content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ) + + {:not_found, reason} -> + {:error, reason} + end + end + @doc """ get object(:contact,:company) by id """ @@ -439,8 +466,7 @@ defmodule Hubspot.Manage.Client do property_name, property_values, limit \\ 10 - ) - when object_type in @standard_objects_types do + ) do client_code |> Token.get_client_access_token(refresh_token) |> case do @@ -505,8 +531,7 @@ defmodule Hubspot.Manage.Client do property_name, property_value, properties \\ [] - ) - when object_type in @standard_objects_types do + ) do client_code |> Token.get_client_access_token(refresh_token) |> case do From b07e3688de703b5cb6e7d57e6dfc439de0fce17d Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Sun, 2 Nov 2025 12:51:10 +0200 Subject: [PATCH 11/16] Remove no need function --- lib/hubspot/manage/client.ex | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index 3d0392d..3571dcd 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -125,9 +125,6 @@ defmodule Hubspot.Manage.Client do end end - def list_custom_properties(_client_code, _refresh_token, _object_type), - do: {:error, "only :contact or :company objects are supported"} - @doc """ Get client info """ From 26c901bf629f53d6256d8b984f143c220686775f Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 4 Nov 2025 10:05:40 +0200 Subject: [PATCH 12/16] Adapt search_objects for pages fetch --- lib/hubspot/manage/client.ex | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index 4d84d68..65ef88b 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -423,7 +423,7 @@ defmodule Hubspot.Manage.Client do API.request( :get, - "crm/v3/objects/#{to_object_type(object_type)}/#{object_id}?#{query_params}", + "crm/v3/objects/#{object_type}/#{object_id}?#{query_params}", nil, [ {"Content-type", "application/json"}, @@ -663,6 +663,31 @@ defmodule Hubspot.Manage.Client do end end + @spec search_objects(String.t(), String.t(), String.t(), map()) :: + {:ok, map()} | {:error, map()} + def search_objects(client_code, refresh_token, object_type, request_body) do + with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), + {:ok, %{body: body}} <- + API.request( + :post, + "crm/v3/objects/#{object_type}/search", + Jason.encode!(request_body), + [ + {"Content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ) do + {:ok, body} + else + {:not_found, reason} -> + {:error, reason} + + error -> + error + end + end + defp get_standard_objects(), do: Enum.map(@standard_objects_types, &to_object(&1, :standard_object)) From c63e233f454c241be9d90c095b8928eed70f1ec3 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 4 Nov 2025 10:11:40 +0200 Subject: [PATCH 13/16] Fix spec define --- lib/hubspot/manage/client.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index 65ef88b..4b9a611 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -380,7 +380,7 @@ defmodule Hubspot.Manage.Client do end end - @spec get_object_by_email(String.t(), String.t(), String.t(), String.t(), String.t()) :: + @spec get_object_by_email(String.t(), String.t(), String.t(), String.t(), list()) :: {:ok, map()} | {:error, map()} def get_object_by_email( client_code, From eddb4675275b6e80c0809eb99b027475d9a278c2 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 25 Nov 2025 15:41:53 +0200 Subject: [PATCH 14/16] adapts update_objects --- lib/hubspot/manage/client.ex | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/hubspot/manage/client.ex b/lib/hubspot/manage/client.ex index 4b9a611..21b9210 100644 --- a/lib/hubspot/manage/client.ex +++ b/lib/hubspot/manage/client.ex @@ -688,6 +688,29 @@ defmodule Hubspot.Manage.Client do end end + @spec update_objects(String.t(), String.t(), String.t(), list()) :: + {:ok, map()} | {:error, map()} + def update_objects(client_code, refresh_token, object_type, inputs) + when is_list(inputs) do + with {:ok, token} <- Token.get_client_access_token(client_code, refresh_token), + request_body <- Map.new() |> Map.put("inputs", inputs), + {:ok, encoded_request_body} <- + Jason.encode(request_body), + {:ok, %{body: response_body}} <- + API.request( + :post, + "crm/v3/objects/#{object_type}/batch/update", + encoded_request_body, + [ + {"Content-type", "application/json"}, + {"authorization", "Bearer #{token}"}, + {"accept", "application/json"} + ] + ) do + {:ok, response_body} + end + end + defp get_standard_objects(), do: Enum.map(@standard_objects_types, &to_object(&1, :standard_object)) From 267420cedf4924d6ced8d861361a60ee8149e506 Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 2 Dec 2025 10:39:13 +0200 Subject: [PATCH 15/16] fix after cachex v4 --- lib/hubspot/auth/management/token.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/hubspot/auth/management/token.ex b/lib/hubspot/auth/management/token.ex index f20e443..d24b438 100644 --- a/lib/hubspot/auth/management/token.ex +++ b/lib/hubspot/auth/management/token.ex @@ -11,14 +11,13 @@ defmodule Hubspot.Auth.Manage.Token do Cachex.fetch(:hubspot_cache, client_code <> "_scopes", fn _key -> case generate_new_access_token(refresh_token) do {:ok, %{"scopes" => scopes}} -> - {:commit, scopes} + {:commit, scopes, expire: @ttl} error -> {:ignore, "Failed to generate an access token for Hubspot OAuth management API for client with code #{client_code} with error #{inspect(error)}"} end end) - |> maybe_set_cache(client_code) |> normalize_cache_fetch() end From 405a29ec52ab483f53a5b72eb1b1e70123e0b6ca Mon Sep 17 00:00:00 2001 From: Waseem Awashra Date: Tue, 23 Dec 2025 09:36:13 +0200 Subject: [PATCH 16/16] Set TTL to 25 minutes --- lib/hubspot/auth/management/token.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/hubspot/auth/management/token.ex b/lib/hubspot/auth/management/token.ex index d24b438..7f306d3 100644 --- a/lib/hubspot/auth/management/token.ex +++ b/lib/hubspot/auth/management/token.ex @@ -4,7 +4,8 @@ defmodule Hubspot.Auth.Manage.Token do use Hubspot.Common.Config - @ttl :timer.seconds(1_800) + # Set TTL to 25 minutes + @ttl :timer.seconds(1_500) @spec get_client_scopes(any, any) :: {:not_found, any} | {:ok, any} | {:commit, any} def get_client_scopes(client_code, refresh_token) do