hey oliviac! had this same issue a while back. hubspot expects form data, not json for oauth. try changing the content-type to ‘application/x-www-form-urlencoded’ and use http_build_query($payload) instead of json_encode. it worked for me!
Yeah, it’s definitely the content-type header like everyone said. Hit this same error last month with a HubSpot integration. Their docs are misleading - most APIs take JSON but HubSpot’s OAuth endpoint wants form-encoded data. Change the content-type and double-check your redirect_uri matches exactly what’s in your HubSpot app settings. They’re picky about trailing slashes and query params. Also make sure your auth code hasn’t expired - they only last 30 seconds after redirect. Once you get the token exchange working, add error handling for token refresh since access tokens die every 30 minutes.
Exactly what alexj said. HubSpot’s OAuth is super picky about format.
I’ve hit this same wall countless times. Always comes down to APIs wanting form data but getting JSON.
I stopped fighting these auth headaches manually. Now I automate the whole OAuth flow - token exchange, refresh handling, error management. Set it up once and you’re done.
I use Latenode for integrations like this. Handles all the OAuth mess automatically and connects straight to HubSpot. No more debugging content types or payload formats. Token refreshes happen in the background too.
Saves me hours every time I hit a new API with weird auth requirements.
That BAD_GRANT_TYPE error happens because you’re sending JSON when HubSpot’s OAuth endpoint wants form-encoded data. I hit this exact problem on a client project last year - took me forever to figure out.
The issue’s in your CURL setup. You’re setting Content-Type to application/json and using json_encode on your payload. HubSpot’s token endpoint won’t accept this format.
Here’s the fix: ditch the JSON content-type header completely and swap json_encode($payload) with http_build_query($payload). CURL will automatically set the right application/x-www-form-urlencoded header.
One more thing - HubSpot’s authorization codes expire super fast, so process them right after the redirect. Fix the content-type and your token exchange should work fine.