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

401 Error for which I don't understand the reason

SOLVED
Go to solution
JasonS9
Campaign Collaborator
0 Votes

I am trying to unsubscribe our clients from our email list when they enter information into a form on our website.  It’s a VB.Net application written in Visual Studio 2005.

 

The pertinent bit of code is this:  (DIMS done earlier in the code for this bit)

 

        If Me.TextBox1.Text > "" Then

            returnxml = DRM.CheckCCEmail(Me.TextBox1.Text.ToString)

            mdoc = New XmlDocument

            mdoc.LoadXml(returnxml)

            Dim namemngr As New XmlNamespaceManager(mdoc.NameTable)

            namemngr.AddNamespace("client", "http://www.w3.org/2005/Atom")

            namemngr.AddNamespace("contact", "http://ws.constantcontact.com/ns/1.0/ ")

            Dim Root As XmlNode = mdoc.SelectSingleNode("client:feed", namemngr)

            mnode = mdoc.SelectSingleNode("//client:entry/client:content/contact:Contact/contact:Status", namemngr)

            Try

                If mnode.InnerText.Contains("Active") Then

                    mnode.InnerText = "Do Not Mail"

                    'mdoc.DocumentElement("//client:entry/client:content/contact:Contact/contact:Status").InnerText = "Do Not Mail"

                    'mnode = mdoc.SelectSingleNode("//client:entry/client:content/contact:Contact/contact:Status", namemngr)

                    mdoc.WriteTo(xw)

                    returnxml = sw.ToString

                    FinalResult = DRM.ExecuteCCUrl(Me.TextBox1.Text.ToString, returnxml)

                End If

            Catch ex As Exception

                Dim error1 As String = ex.Message

                Me.Label1.Visible = True

            End Try

 

        End If

 

Everything works fine when I retrieve the client’s info.  I can change the status to Do Not Mail from Active no problem.

 

However when I try to post that information back to the constant contact api, I invariably get a 401 not authorized error.

 

I’m talking to the API on both sides of this and supplying authorization credentials the same way  on both sides so I’m baffled why my requests would be authorized in the first call of the API but not authorized in my subsequent post of the same data (simply modified to show Do Not Mail as the status) back to the API.  I’m given to understand this is the preferred method of effecting changes.

 

I’ve included both calls to the api below so you can see that I am basically using the same methods both times except that the first time  I’m issuing a GET and the second call is a PUT operation.

 

I’ve replaced our APIkey, username and password data with XXXX but I can assure you they are the same in both pieces of code when they get executed.

 

 These are the bits of the DRM object that gets called above that handle talking to the API.

 

Public Shared Function CheckCCEmail(ByVal emailadd As String) As String

        Dim apikey As String = "XXXXXX"

        Dim UName As String = "XXXXX"

        Dim UPass As String = "XXXXX"

        Dim Email As String = HttpUtility.UrlEncode(LCase(emailadd))

        Dim uri As New Uri("https://api.constantcontact.com/ws/customers/ " & UName & "/contacts?email=" & Email)

        Dim LoginCredentials As CredentialCache = New CredentialCache

        LoginCredentials.Add(New Uri("https://api.constantcontact.com/ws/customers/ " & UName), "Basic", New NetworkCredential((apikey + ("%" + UName)), UPass))

        If (uri.Scheme = uri.UriSchemeHttps) Then

            Dim request As HttpWebRequest = HttpWebRequest.Create(uri)

            request.Method = WebRequestMethods.Http.Get

            request.Credentials = LoginCredentials

            Dim response As HttpWebResponse = request.GetResponse()

            Dim reader As New StreamReader(response.GetResponseStream())

            Dim tmp As String = reader.ReadToEnd()

            response.Close()

            Return tmp

        End If

    End Function

 

   Public Shared Function ExecuteCCUrl(ByVal emailadd, ByVal xmldata) As String

        Dim apikey As String = "XXXXX"

        Dim Uname As String = "XXXX"

        Dim UPass As String = "XXXX"

        Dim Email As String = HttpUtility.UrlEncode(LCase(emailadd))

        Dim uri As New Uri("https://api.constantcontact.com/ws/customers/ " & Uname & "/contacts?email=" & Email)

        Dim LoginCredentials As CredentialCache = New CredentialCache

        LoginCredentials.Add(New Uri("https://api.constantcontact.com/ws/customers/ " & Uname), "Basic", New NetworkCredential((apikey + ("%" + Uname)), UPass))

        If uri.Scheme = uri.UriSchemeHttps Then

            Dim request As HttpWebRequest = HttpWebRequest.Create(uri)

            request.ContentLength = xmldata.Length

            request.ContentType = "application/x-www-form-urlencoded"

            request.Method = WebRequestMethods.Http.Put

            Dim writer As New StreamWriter(request.GetRequestStream)

            writer.Write(xmldata)

            writer.Close()

            Dim oResponse As HttpWebResponse = request.GetResponse()

            Dim reader As New StreamReader(oResponse.GetResponseStream())

            Dim tmp As String = reader.ReadToEnd()

            oResponse.Close()

            Return tmp

        Else

            Return "Fail"

        End If

    End Function

 

Thanks in advance for any help.

 

Steve Wells

Programmer and Data Base Administrator

Described and Captioned Media Program

1 ACCEPTED SOLUTION
David_J
Employee
0 Votes

I was able to issue an http delete against a contact id using .NET 2.0 Framework. This code was initially written in C#.NET which I then ran through the .net code converter.

 

 

Dim apiKey As [String] = "APIKEY"
Dim username As [String] = "USERNAME"
Dim password As [String] = "PASSWORD"
Dim contactId As [String] = "https://api.constantcontact.com/ws/customers/" & username & "/contacts/1"

Dim credentials As New NetworkCredential(apiKey & "%" & username, password)
Dim ctctRequest As WebRequest = WebRequest.Create(contactId)
ctctRequest.Method = "DELETE"
ctctRequest.Credentials = credentials

Dim ctctResponse As HttpWebResponse = TryCast(ctctRequest.GetResponse(), HttpWebResponse)
Dim responseStream As New StreamReader(ctctResponse.GetResponseStream())
Dim responseText As String = responseStream.ReadToEnd()
Dim responseCode As Integer = CInt(ctctResponse.StatusCode)
ctctResponse.Close()
responseStream.Close()

MessageBox.Show(responseCode & responseText)

 

Please note that you need to insert your username, password, and apiKey in order for this to work. Also, this is currently set to delete contact "1" and you would want to adjust this to delete the appropriate contact.

 

I hope this helps. Please let me know if you have any other questions or concerns, or if this does not work for you. Thanks!

David J

View solution in original post

8 REPLIES 8
DaveBerard
Employee
0 Votes

Status field of a Contact is read-only and is not able to be modified.  That said, this should not result in a 401 error. 

 

If you're looking to move a Contact to Do Not Mail, you would need to execute a Delete request (https://community.constantcontact.com/t5/Documentation/Opting-out-a-Contact/ba-p/25117) on the Contact URI.  This will change their status from Active to Do Not Mail.

 

The 401 response is only returned if there is a failure in authentication in some way.  This could include an invalid API key, invalid API key/username format or an actual bad username and/or password combination.  I would recommend taking VB out of the equation and trying to send a request manually through an open source RESTClient (such as: https://community.constantcontact.com/t5/Documentation/How-To-Use-RESTClient/ba-p/24915) to see if you can make the request work with just a simple tool.  Once you have everything working correctly there, it is usually pretty easy to see where in a code flow the problem is failing.

Dave Berard
Senior Product Manager, Constant Contact
JasonS9
Campaign Collaborator
0 Votes

So, I shouldn't have relied on the information here:  https://community.constantcontact.com/t5/Documentation/Updating-Contact-Information/ba-p/25055

 

which states 

To update a contact's information, the best way is to get details of an existing contact, modify the details, and use the resulting XML in a PUT method against the following URI: and

 

The request body you use must contain all elements that belong to a contact, which are returned through the GET method.  By using this XML, you should modify only the information you would like to update, then pass the XML in your request.

 

To modify the contact's subscriptions to different contact lists, you need to update contents of <ContactLists> element.

 

You may want to update that information to make it clear that there are elements of the info you get that you cannot modify according to the directions given at the link supplied above.

JasonS9
Campaign Collaborator
0 Votes

The link you provided says I should use the delete method but doesn't give any details on what form the delete method needs to be in when I send it. 

 

Here's what it says:

 

A Contact is opted-out by issuing the DELETE method on the contact resource:

 

https://api.constantcontact.com/ws/customers/{user​name}/contacts/{contact-id}

The call will return 204 No Content if it succeeds. This should be used if the contact has decided to unsubscribe from receiving all emails or has asked to stop sending all emails.  Opted-out contacts become members of the Do-Not-Mail special list and is removed from all other contact lists.

 

Is there a link that documents what the xml is that needs to be sent to the API?

David_J
Employee
0 Votes

In order to delete that contact, you would need to issue an http delete to that contacts id, ie:

 

https://api.constantcontact.com/ws/customers/{user​name}/contacts/{contact-id}

There is no XML request that would need to be sent as the request body for this type of request. If I am misunderstanding your question please let me know and I will do my best to clarify. Thanks.

David J

JasonS9
Campaign Collaborator
0 Votes

I understand what you are saying now.  You mean to do an http DELETE.  Problem with that is that .NET 2.0's httpwebrequest object doesn't support the DELETE method.  You can do GET, POST, PUT, CONNECT, etc. with it but it does not provide any support for the DELETE method.

 

I've tried this code: 

 

 Dim apikey As String = "45911072-77d5-4c67-84b3-a657edba8629"
        Dim Uname As String = "DCMPNews"
        Dim UPass As String = "xxxxxxxxxxxxxxxxxx"
        Dim Email As String = HttpUtility.UrlEncode(LCase(emailadd))
        Dim uri As New Uri("https://api.constantcontact.com/ws/customers/" & Uname & "/contacts/ & contactID")
        Dim LoginCredentials As CredentialCache = New CredentialCache
        LoginCredentials.Add(New Uri("https://api.constantcontact.com/ws/customers/" & Uname), "Basic", New NetworkCredential((apikey + ("%" + Uname)), UPass))
        If uri.Scheme = uri.UriSchemeHttps Then
            Dim request As HttpWebRequest = HttpWebRequest.Create(uri)
            request.ContentLength = 0
            request.ContentType = "application/xml"
            request.Method = "DELETE"
            Dim oResponse As HttpWebResponse = request.GetResponse()
            Dim reader As New StreamReader(oResponse.GetResponseStream())
            Dim tmp As String = reader.ReadToEnd()
            oResponse.Close()
            Return tmp
        Else
            Return "Fail"
        End If

 

Because I saw a forum posting elsewhere where someone said they got the HTTPWEBREQUEST object to do a delete doing it this way.  However, I still get the 401 - not authorized error when I try this.

 

I'm going to try using this sample XML with a POST method to see if this works. 

 

<entry xmlns="http://www.w3.org/2005/Atom">
<id>http://api.constantcontact.com/ws/customers/joesflowers/contacts/101 </id>
<title type="text">Contact: u86597@example.com</title>
<updated>2008-04-25T19:29:06.096Z</updated>
<author> </author>
<content type="application/vnd.ctct+xml">
<Contact xmlns="http://ws.constantcontact.com/ns/1.0/ " id="http://api.constantcontact.com/ws/customers/joesflowers/contacts/101 ">
<EmailAddress>u86597@example.com</EmailAddress>
<OptInSource>ACTION_BY_CUSTOMER</OptInSource>
<ContactLists>
</ContactLists>
</Contact>
</content>
</entry>

 

substituting my username, etc.

 

I know this is not the right thing to do but we only have 1 list (and considering the difficulty I have getting the API to work I'm pretty sure we will never have more than one list) so maybe this will work for us for now.

David_J
Employee
0 Votes

I was able to issue an http delete against a contact id using .NET 2.0 Framework. This code was initially written in C#.NET which I then ran through the .net code converter.

 

 

Dim apiKey As [String] = "APIKEY"
Dim username As [String] = "USERNAME"
Dim password As [String] = "PASSWORD"
Dim contactId As [String] = "https://api.constantcontact.com/ws/customers/" & username & "/contacts/1"

Dim credentials As New NetworkCredential(apiKey & "%" & username, password)
Dim ctctRequest As WebRequest = WebRequest.Create(contactId)
ctctRequest.Method = "DELETE"
ctctRequest.Credentials = credentials

Dim ctctResponse As HttpWebResponse = TryCast(ctctRequest.GetResponse(), HttpWebResponse)
Dim responseStream As New StreamReader(ctctResponse.GetResponseStream())
Dim responseText As String = responseStream.ReadToEnd()
Dim responseCode As Integer = CInt(ctctResponse.StatusCode)
ctctResponse.Close()
responseStream.Close()

MessageBox.Show(responseCode & responseText)

 

Please note that you need to insert your username, password, and apiKey in order for this to work. Also, this is currently set to delete contact "1" and you would want to adjust this to delete the appropriate contact.

 

I hope this helps. Please let me know if you have any other questions or concerns, or if this does not work for you. Thanks!

David J

JasonS9
Campaign Collaborator
0 Votes

That worked.  Thanks.

 

I guess I'll never know why code that worked for a get didn't work for a delete.

 

 

JasonS9
Campaign Collaborator
0 Votes

I downloaded the REST client and tried to use it.  I entered the following URL

 

https://api.constantcontact.com/ws/customers/DCMPNews/contacts/3107

 

and specified the DELETE method

 

I entered my apiKey + % + username and my password in the indicated fields on the AUTH tab of the REST Client 2.3.3

 

I got a 204 - no content so I guess that was the expected response.  However, knowing that doesn't get me any closer to knowing why I get a 401 - not authorized error when I do the same thing from VB.NET.  I'm using exactly the same code that I use when I get the client's info with a GET method.  That method works every time and I get the expected XML back.

 

So, any ideas why the same credentials don't work when I'm using a DELETE or  a POST method?

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