Skip to main content

Really weird behaviour here that I can’t get to the bottom of; I have a method which I use to render klaviyo templates, and use this in several projects with no issue. However, in our most recent I’m getting a 400 error on the API call - despite the identical call on Postman working and getting a response.

I’ve verified that the Authorization header, indeed all headers, match the Postman call for my own sanity (they do), and have even just reduced the context down to a single key/value pair to ensure there’s nothing weird going on with content.

However, the 400 issue persists; if I switch on enhanced logging I get the following:

*   Trying 2606:4700:4400::ac40:9377:443...
* Connected to a.klaviyo.com (2606:4700:4400::ac40:9377) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: /etc/ssl/certs
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=a.klaviyo.com
* start date: Jul 22 05:46:38 2024 GMT
* expire date: Oct 20 05:46:37 2024 GMT
* subjectAltName: host "a.klaviyo.com" matched cert's "a.klaviyo.com"
* issuer: C=US; O=Google Trust Services; CN=WE1
* SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x5568b69836f0)
> post /api/template-render HTTP/2
Host: a.klaviyo.com
authorization: Klaviyo-API-Key pk_xxx
accept: application/json
content-type: application/json
revision: 2024-07-15
content-length: 108

* We are completely uploaded and fine
* old SSL session ID is stale, removing
< HTTP/2 400
< server: cloudflare
< date: Thu, 22 Aug 2024 09:57:51 GMT
< content-type: text/html
< content-length: 155
< cf-ray: -
* The requested URL returned error: 400
* stopped the pause stream!
* Connection #0 to host a.klaviyo.com left intact

For completeness, here are my headers and body:

INFO - 2024-08-22 11:08:12 --> Header: Authorization: Authorization: Klaviyo-API-Key pk_xxx
INFO - 2024-08-22 11:08:12 --> Header: Accept: Accept: application/json
INFO - 2024-08-22 11:08:12 --> Header: Content-Type: Content-Type: application/json
INFO - 2024-08-22 11:08:12 --> Header: revision: revision: 2024-07-15


{
"data": {
"type": "template",
"id": "US2baB",
"attributes": {
"context": {
"salutation": "Hi Test",
"detail": "TEST"
}
}
}
}

As I mentioned, the method I have is identical in 3 other projects and works no problem there, so I’m a bit stumped as to why this time it’s working in Postman (so not an auth or formatting issue) but not in my project.

Hello @Verdant Spark Which Programming language you are using to call the API? Is it possible to share the code to check where is the issue?


Hey there

Here’s the method; as I mentioned, I’m not actually questioning this, as it’s from a library we have in 3 other projects where it’s working just fine - there’s something about this particular klaviyo account - not sure if it’s because they’re not paying for the account yet, though that wouldn’t explain why it works from Postman.

The project is a CI4 framework:

/**
* @param $template
* @param $first_name
* @param $passed_data array|null
* @return mixed
*/
public function klaviyoRender( $template, $first_name, ?array $passed_data = null ): mixed {
$salutation = !$first_name || $first_name == '' ? '' : "Hi $first_name";
$context = new stdClass();
$context->salutation = $salutation;
if ( $passed_data ) {
foreach ( $passed_data as $key => $value ) {
$context->$key = $value;
}
}
$url = "https://a.klaviyo.com/api/template-render";
$body =
'data' =>
'type' => 'template',
'id' => $template,
'attributes' => t
'context' => $context
]
]
];
$client = Services::curlrequest();
$auth = 'Klaviyo-API-Key ' . getenv( 'KLAVIYO_API_KEY' );
$client->setHeader( 'Authorization', $auth );
$client->setHeader( 'Accept', 'application/json' );
$client->setHeader( 'Content-Type', 'application/json' );
$client->setHeader( 'revision', '2024-07-15' );
$client->setBody( json_encode( $body ) );

try {
$response = $client->request( 'post', $url, ( 'debug' => WRITEPATH . 'logs/curl_log.txt' ] );
return json_decode( $response->getBody() )->data->attributes->html;
}
catch ( Exception $e ) {
$this->logError($e);
return false;
}
}

 


Hello @Verdant Spark  I don’t see any issue with code.
Did you try echoing the output of json_encode( $body )?


Yes - body is set as expected (you can see the body in the initial post)


Hmm, this is curious…

If this isn’t an encoding issue, I think it must be an API key or ID issue… are you sure that template with that ID exists in this environment? And can you triple-check the API key is the expected one for this klaviyo instance?

Do you get any more data in your error message? Typically a 400-level error will have some response body (or response text) that gives a little bit more information.

For example, my application hit a 409 error this morning and I got the following message returned in the error response.

text: '{"errors":"{"id":"9ab0eb5d-ab41-462d-afab-7168c146c0c3","status":409,"code":"duplicate_profile","title":"Conflict.","detail":"A profile already exists with one of these identifiers.","source":{"pointer":"/data/attributes"},"links":{},"meta":{"duplicate_profile_id":"XXX"}}]}';

Let us know.

 

Cheers,

Kevin.


API and template key are good - I’m using same in Postman and get a response 😕 The 400 doesn’t seem to come with any meaningful message that I can surface.


Just to make sure I’m not batshit crazy I also created a new API key and swapped it out to check. That has the same behaviour.


Weird!! Have you tried other API calls within this klaviyo instance? Simple calls like fetching a profile?


Super weird. I’ve swapped out my method for using the klaviyo SDK and now it’s working. So there’s a header which the API now expects to get (or not) which must have been causing it to fail. So random, and undocumented - but at least it’s working with the SDK instead. Sigh. There goes 6 hours of my life I’m not getting back.


Reply