The Final Week of the Ready, Set, Send Challenge Has Begun!

400 Bad Request when creating new contact from form

SOLVED
Go to solution
WillM68
Campaign Contributor
0 Votes

I am receiving a 400 bad request when I attempt to submit a HttpClient.PostAsJsonAsync() from my asp.NET MVC web application. I have the following class definitions

 

public class ConstantContact
    {
        public ConstantContact()
        {
            Custom_Fields = new List<CustomField>();
            Lists = new List<ContactList>();
            Email_Addresses = new List<EmailAddress>();
        }
        public List<CustomField> Custom_Fields { get; set; }

        public string Cell_Phone { get; set; }

        public string First_Name { get; set; }

        public string Last_Name { get; set; }

        public List<ContactList> Lists { get; set; }

        public List<EmailAddress> Email_Addresses { get; set; }
    }

public class CustomField
    {
        public string name { get; set; }

        public string value { get; set; }
    }

public class ContactList
    {
        public string Id { get; set; }
    }

public class EmailAddress
    {
        public string Opt_In_Source { get; set; }

        public string Email_Address { get; set; }
    }

public class ContactModel
    {
        [StringLength(160, ErrorMessage = "{0} is too long.")]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }

        [StringLength(160, ErrorMessage = "{0} is too long.")]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }

        [Required(ErrorMessage = "{0} is required.")]
        [RegularExpression(@"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$", ErrorMessage = "{0} is invalid")]
        [Display(Name = "Email")]
        public string EmailAddress { get; set; }

        [Required(ErrorMessage = "{0} is required.")]
        [RegularExpression(@"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$", ErrorMessage = "{0} is invalid")]
        [Display(Name = "Confirm")]
        [Compare("EmailAddress", ErrorMessage = "Confirm does not match.")]
        public string ConfirmEmail { get; set; }

        [Display(Name = "Birth Date")]
        public string BirthDate { get; set; }

        public readonly string SuccessMessage = "<h4>Thank you!</h4>You have been added to our mailing list. Please check your inbox for a welcome message.";

        public readonly string FailureMessage = "<h4>Oops!</h4>Something went wrong with your message. Please try again later.";

        public readonly string ValidationMessage = "<h4>Oops!</h4>Please fix the errors and try again.";
    }

 

When the form gets submitted the controller calls to the task layer to build the request as follows

 

var contact = CreateConstantContact(model);
                    var client = new HttpClient();
                    client.DefaultRequestHeaders.Add("Authorization", "Bearer [Access_Token]");
                    client.DefaultRequestHeaders.Add(HttpRequestHeader.ContentType.ToString(), "application/json");
                    var response = client.PostAsJsonAsync("https://api.constantcontact.com/v2/contacts?api_key=[api_key]", contact).Result;
                    if (response.IsSuccessStatusCode)
                    {
                        TempData["Feedback"] = model.SuccessMessage;
                        TempData["UpdateStatus"] = true;
                    }
                    else
                    {
                        TempData["Feedback"] = "Status: " + response.StatusCode + " - " + response.ReasonPhrase + "<br />" + response.RequestMessage.Content;
                        TempData["UpdateStatus"] = false;
                    }

private ConstantContact CreateConstantContact(ContactModel model)
        {
            var contact = new ConstantContact();

            if (string.IsNullOrEmpty(model.BirthDate) == false)
            {
                var customs = new CustomField { name = "CustomField1", value = model.BirthDate };
                contact.Custom_Fields.Add(customs);
            }
            if (string.IsNullOrEmpty(model.FirstName) == false)
            {
                contact.First_Name = model.FirstName;
            }
            if (string.IsNullOrEmpty(model.LastName) == false)
            {
                contact.Last_Name = model.LastName;
            }
            var list = new ContactList { Id = "2" };
            contact.Lists.Add(list);
            var email = new EmailAddress { Email_Address = model.EmailAddress, Opt_In_Source = "ACTION_BY_VISITOR" };
            contact.Email_Addresses.Add(email);

            return contact;
        }

 

I have tried a plethora of JSON serializers with absolutely no luck. Always receive 400 Bad Request. Is there something that I have not setup properly or am I missing something? Any suggestions will be much appreciated.

 

Thanks,

 

Will

 

 

1 ACCEPTED SOLUTION
Shannon_W
Employee
0 Votes

Update: This issue might still be caused by the way that the .net library concatenates headers, but a comma shouldn't work, as it can separate multiple values (for the same key) in the header key-value pairing.

 

Hi,

 

I'm going to have this looked into on our side, but it looks like order might matter for us when you provide the headers, which wasn't intended.  If headers are combined to result in the following when the request is sent to us, the request goes through:

 

"Authorization: Bearer accessTokenValue,Content-type: application/json"

 

If the headers are combined to result in the following, with the order reversed, a 401 error results:

 

"Content-type: application/json,Authorization: Bearer accessTokenValue"

 

It looks like if a line break is provided between the two header values, everything works.  You can probably get around this strange ordering issue for now by either reversing the order in which you add headers, or providing the access token in the URI.  If this fixes the issue, we can surmise that the .net library adds header values to the beginning of the header string and uses comma separation.

 

Let me know if doing either of the above (changing the header order or providing the access token in the URI) doesn't fix the issue.  Sorry for the inconvenience.

 

Best Regards,

Shannon W.

API Support Specialist

View solution in original post

9 REPLIES 9
Shannon_W
Employee
0 Votes

Hi Will,

 

Is there any way for you to get and post the JSON that you end up sending to us when this code is run?  Looking at that and comparing it to the payload we expect & the key-value pairs we accept is going to be the easiest way to see what is causing the 400.

 

Thanks!

 

Best Regards,

Shannon W.

API Support Specialist

WillM68
Campaign Contributor
0 Votes

I was able to capture the serialized string that is included in the request. It is taken straight from the debugger so the string is escaped.

 

"{\"Custom_Fields\":[{\"name\":\"CustomField1\",\"value\":\"10/18/1981\"}],\"First_Name\":\"Will\",\"Last_Name\":\"Morrison\",\"Lists\":[{\"Id\":\"2\"}],\"Email_Addresses\":[{\"Opt_In_Source\":\"ACTION_BY_VISITOR\",\"Email_Address\":\"will@gmail.com\"}]}"

 

Let me kow if this helps or is you need more information.

WillM68
Campaign Contributor
0 Votes

It appears as though the capitalization of property fields were the cause of the bad request errors. However, once that was fixed I have been receiving Forbidden responses from Mashery saying that my API is inactive or invalid.

 

I have been using the API key copied straight from my Mashery login and the Authorization Access Token Bearer obtained from the Mashery IO Docs. When I simulate the add new contact request using Fiddler the POST processes with no issues just using the API key and Bearer token. I shouldnt need to autenticate just to add a contact???

 

 

Shannon_W
Employee
0 Votes

Hi Will,

 

You do need to authenticate to add a contact to your account, but that is accomplished by providing your API Key and access token.  I can't find a problem with the way your script authenticates, but you could authenticate entirely in the URI, like so:

 

https://api.constantcontact.com/v2/contacts?api_key=[apiKey]&access_token=[accessToken]

 

Are you able to make successful sample calls on the IO Docs page using your api key and access token?  If not, send us an email at webservices@constantcontact.com so that we can verify that there isn't an issue with either value.  

 

Best Regards,

Shannon W.

API Support Specialist

WillM68
Campaign Contributor
0 Votes

Thanks for the quick reply Shannon. I apologize, of course I need to authenticate to add contacts. What I meant to ask is, Do I need to authenticate through Mashery as well? Sorry for the confusion.

 

I have been able to add contacts successfully through the IO Docs interface. I have also been able to add contacts using the Fiddler2 application.

Shannon_W
Employee
0 Votes

Update: This issue might still be caused by the way that the .net library concatenates headers, but a comma shouldn't work, as it can separate multiple values (for the same key) in the header key-value pairing.

 

Hi,

 

I'm going to have this looked into on our side, but it looks like order might matter for us when you provide the headers, which wasn't intended.  If headers are combined to result in the following when the request is sent to us, the request goes through:

 

"Authorization: Bearer accessTokenValue,Content-type: application/json"

 

If the headers are combined to result in the following, with the order reversed, a 401 error results:

 

"Content-type: application/json,Authorization: Bearer accessTokenValue"

 

It looks like if a line break is provided between the two header values, everything works.  You can probably get around this strange ordering issue for now by either reversing the order in which you add headers, or providing the access token in the URI.  If this fixes the issue, we can surmise that the .net library adds header values to the beginning of the header string and uses comma separation.

 

Let me know if doing either of the above (changing the header order or providing the access token in the URI) doesn't fix the issue.  Sorry for the inconvenience.

 

Best Regards,

Shannon W.

API Support Specialist

WillM68
Campaign Contributor
0 Votes

After switching the header order, it still gave me the same error. I was finally able to make a successful request by removing ALL the headers (Authentication, Action-By) and placing the bearer token information in the URL query.

 

I wish that I didn't have to remove the Action-By header so that I could keep track of the contacts that registered themselves online vs. the contacts entered manually. Is there a way that I could stay in the loop if a fix for this is released?

 

Thanks again for all fo your help Shannon.

 

Will M.

Shannon_W
Employee
0 Votes

Hi Will,

 

I wasn't able to reproduce anything that I would consider a defect on our end.  When the authentication is provided to us according to http guidelines, I don't see any difference no matter how I order the headers.  

 

I think there is something wrong with how the library is providing the headers to us.  The library [I think] you're using has a built in Authorization method.  Did you ever try something like below to set the Authentication?

 

client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new Authorization( "OAuth", accessToken );

 

The api key always has to be provided to us in the URI, but I would remove the access token query parameter and try the above lines for Authentication.  

 

Best Regards,

Shannon W.

API Support Specialist

Shannon_W
Employee
0 Votes

I also see this as a possibility for authentication with OAuth2:

 

HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("OAuth", "token");

 

 

Best Regards,

Shannon W.

API Support Specialist

Resources
Developer Portal

View API documentation, code samples, get your API key.

Visit Page

Announcements

API Updates

Join our list to be notified of new features and updates to our V3 API.

Sign Up