Example Implementations
When interacting with Datadome a good TLS Client is required
Note that the interstitial challenge will return a json, with other than the cookie, an url to follow. In case of hard-protection mode on datadome, you may get redirected to a captcha challenge that needs to be solved as well
from tls_client import Session
from requests import post
if __name__ == "__main__":
API_KEY = "TAKION_API_XXXX"
session = Session("chrome_120")
page_url = 'https://www.footlocker.pt/en/product/~/314206535404.html'
page_headers = {
'Host': 'www.footlocker.pt',
'cache-control': 'max-age=0',
'sec-ch-device-memory': '8',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-arch': '"arm"',
'sec-ch-ua-platform': '"macOS"',
'sec-ch-ua-model': '""',
'sec-ch-ua-full-version-list': '"Not/A)Brand";v="8.0.0.0", "Chromium";v="126.0.6478.127", "Google Chrome";v="126.0.6478.127"',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-language': 'en-GB,en;q=0.9',
'priority': 'u=0, i',
}
# 1. Load the page
response = session.get(page_url, headers=page_headers)
# 2. Check for Datadome
if not (("geo.captcha-delivery.com" in response.text or \
"interstitial.captcha-delivery.com" in response.text) \
and response.status_code == 403): # Check if we need to solve a challenge
print("Page loaded successfully")
exit(0)
# 3. Generate challenge url
response = post(
"https://datadome.takionapi.tech/build",
headers={
"x-api-key": API_KEY,
},
json={
"datadome": session.cookies.get_dict().get("datadome"),
"referer": page_url,
"html": response.text
}
).json()
if response.get("error"):
print("Task failed with error => %s" % response["error"])
exit(0)
challenge_url = response["url"]
# 4. Solve the challenge
print(f"Solving {response['challenge_type']} challenge...")
challenge = session.get(
challenge_url,
headers={
'Host': 'geo.captcha-delivery.com',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Sec-Fetch-Site': 'cross-site',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Dest': 'iframe',
'Referer': 'https://www.footlocker.pt/en/product/~/314206535404.html',
'Accept-Language': 'en-GB,en;q=0.9',
}
)
data = post(
f"https://datadome.takionapi.tech/solve",
headers={
"x-api-key": API_KEY,
"User-Agent": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
},
json={
"html": challenge.text
}
).json()
if error := data.get("error"):
print("Task failed with error => %s" % error)
exit(0)
# 5. Post the solution to Datadome
print("Posting solution...")
# For interstitial is a POST request, footlocker only uses it
res = session.post(
data["url"],
data=data["payload"],
headers={
# Headers took from our browser
'Host': 'geo.captcha-delivery.com',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-platform': '"macOS"',
'sec-ch-ua-mobile': '?0',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': '*/*',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Dest': 'empty',
'Referer': 'https://geo.captcha-delivery.com/captcha/?initialCid=AHrlqAAAAAMAyaXgoxstywMAs2mAaw%3D%3D&hash=EC3B9FB6F2A31D3AF16C270E6531D2&cid=WnaMs0Z7esaErwyCZ5bgStxks4ZNPTM43srPi1nPou7gj6cYdKWPr3WMHkqqO1GS3v8Dnbg5JN4O1l4lMScX9TesXJvvy67uqtRjDIzLM8dap3xjH~zSxlrIZeFcyzaG&t=fe&referer=https%3A%2F%2Feuro2024-sales.tickets.uefa.com%2F&s=43337&e=511a3fab6473017b5c67ee503b9dc67701795c127fc3898022ea13fb22e74965&dm=cd',
'Accept-Language': 'en-GB,en;q=0.9',
}
)
data = res.json()
if cookie := data["cookie"].split("datadome=")[1].split("; ")[0]:
print(f"Got datadome cookie => {cookie[:30]}")
session.cookies.set("datadome", cookie)
# 6. Load the page again
response = session.get(page_url, headers=page_headers)
print(response)
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/url"
"strings"
http "github.com/bogdanfinn/fhttp"
"github.com/bogdanfinn/fhttp/http2"
tls_client "github.com/bogdanfinn/tls-client"
"github.com/bogdanfinn/tls-client/profiles"
tls "github.com/bogdanfinn/utls"
)
const apiKey = "TAKION_API_XXXX" // Replace with your actual API key
func main() {
requestWithCustomClient()
}
type DatadomeResponse struct {
URL string `json:"url"`
Payload string `json:"payload"`
Cookie string `json:"cookie"`
Error string `json:"error"`
}
type CaptchaSolveResponse struct {
URL string `json:"url"`
Payload json.RawMessage `json:"payload"` // Changed to RawMessage to handle different structures
Error string `json:"error"`
}
type CaptchaSolutionResponse struct {
Cookie string `json:"cookie"`
}
func requestWithCustomClient() {
settings := map[http2.SettingID]uint32{
http2.SettingHeaderTableSize: 65536,
http2.SettingMaxConcurrentStreams: 1000,
http2.SettingInitialWindowSize: 6291456,
http2.SettingMaxHeaderListSize: 262144,
}
settingsOrder := []http2.SettingID{
http2.SettingHeaderTableSize,
http2.SettingMaxConcurrentStreams,
http2.SettingInitialWindowSize,
http2.SettingMaxHeaderListSize,
}
pseudoHeaderOrder := []string{
":method",
":authority",
":scheme",
":path",
}
connectionFlow := uint32(15663105)
specFactory := func() (tls.ClientHelloSpec, error) {
return tls.ClientHelloSpec{
CipherSuites: []uint16{
tls.GREASE_PLACEHOLDER,
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
CompressionMethods: []uint8{
tls.CompressionNone,
},
Extensions: []tls.TLSExtension{
&tls.UtlsGREASEExtension{},
&tls.SNIExtension{},
&tls.ExtendedMasterSecretExtension{},
&tls.RenegotiationInfoExtension{Renegotiation: tls.RenegotiateOnceAsClient},
&tls.SupportedCurvesExtension{Curves: []tls.CurveID{
tls.CurveID(tls.GREASE_PLACEHOLDER),
tls.X25519,
tls.CurveP256,
tls.CurveP384,
}},
&tls.SupportedPointsExtension{SupportedPoints: []byte{
0,
}},
&tls.SessionTicketExtension{},
&tls.ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}},
&tls.StatusRequestExtension{},
&tls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []tls.SignatureScheme{
tls.ECDSAWithP256AndSHA256,
tls.PSSWithSHA256,
tls.PKCS1WithSHA256,
tls.ECDSAWithP384AndSHA384,
tls.PSSWithSHA384,
tls.PKCS1WithSHA384,
tls.PSSWithSHA512,
tls.PKCS1WithSHA512,
}},
&tls.SCTExtension{},
&tls.KeyShareExtension{KeyShares: []tls.KeyShare{
{Group: tls.CurveID(tls.GREASE_PLACEHOLDER), Data: []byte{0}},
{Group: tls.X25519},
}},
&tls.PSKKeyExchangeModesExtension{Modes: []uint8{
tls.PskModeDHE,
}},
&tls.SupportedVersionsExtension{Versions: []uint16{
tls.VersionTLS13,
tls.VersionTLS12,
tls.VersionTLS11,
tls.VersionTLS10,
}},
&tls.UtlsCompressCertExtension{Algorithms: []tls.CertCompressionAlgo{
tls.CertCompressionBrotli,
}},
&tls.ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}},
&tls.UtlsGREASEExtension{},
&tls.UtlsPaddingExtension{GetPaddingLen: tls.BoringPaddingStyle},
},
}, nil
}
customClientProfile := profiles.NewClientProfile(tls.ClientHelloID{
Client: "MyCustomProfile",
Version: "1",
Seed: nil,
SpecFactory: specFactory,
}, settings, settingsOrder, pseudoHeaderOrder, connectionFlow, nil, nil)
options := []tls_client.HttpClientOption{
tls_client.WithTimeoutSeconds(60),
tls_client.WithClientProfile(customClientProfile), // use custom profile here
}
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
if err != nil {
log.Println(err)
return
}
// Step 1: Perform the POST request
diocanurl := "https://mygift.giftcardmall.com/api/card/getCardBalanceSummary"
data := `{"cardNumber": "4358800457414798","expirationMonth": 7,"expirationYear": 2032,"securityCode": "823","rmsSessionId": "7T1C3BFHYTQJ89RN9KZ0AZ2HG8"}`
headers := http.Header{
"Host": {"mygift.giftcardmall.com"},
"sec-ch-ua": {`"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"`},
"accept": {"application/json, text/plain, */*"},
"content-type": {"application/json"},
"sourceorigin": {"https://mygift.giftcardmall.com/"},
"sec-ch-ua-mobile": {"?0"},
"user-agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"},
"sec-ch-ua-platform": {`"Windows"`},
"dnt": {"1"},
"origin": {"https://mygift.giftcardmall.com"},
"sec-fetch-site": {"same-origin"},
"sec-fetch-mode": {"cors"},
"sec-fetch-dest": {"empty"},
"referer": {"https://mygift.giftcardmall.com/"},
"accept-language": {"en-GB,en;q=0.9"},
"priority": {"u=1, i"},
http.HeaderOrderKey: {
"accept",
"accept-encoding",
"accept-language",
"cache-control",
"if-none-match",
"sec-ch-ua",
"sec-ch-ua-mobile",
"sec-ch-ua-platform",
"sec-fetch-dest",
"sec-fetch-mode",
"sec-fetch-site",
"sec-fetch-user",
"upgrade-insecure-requests",
"user-agent",
},
}
req, err := http.NewRequest("POST", diocanurl, bytes.NewBuffer([]byte(data)))
if err != nil {
log.Println(err)
return
}
req.Header = headers
resp, err := client.Do(req)
if err != nil {
log.Println(err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
// Step 2: Check for Datadome
if resp.StatusCode == 403 && (strings.Contains(string(body), "geo.captcha-delivery.com") || strings.Contains(string(body), "interstitial.captcha-delivery.com")) {
log.Println("Datadome challenge detected")
var challenge DatadomeResponse
if err := json.Unmarshal(body, &challenge); err != nil {
log.Println("Failed to parse Datadome challenge:", err)
return
}
log.Println(challenge.URL)
// Step 3: Fetch challenge page
challengeReq, err := http.NewRequest("GET", challenge.URL, nil)
if err != nil {
log.Println(err)
return
}
challengeReq.Header = http.Header{
"Host": {"geo.captcha-delivery.com"},
"sec-ch-ua": {`"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"`},
"sec-ch-ua-mobile": {"?0"},
"sec-ch-ua-platform": {"\"macOS\""},
"Upgrade-Insecure-Requests": {"1"},
"User-Agent": {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"},
"Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"},
"Sec-Fetch-Site": {"cross-site"},
"Sec-Fetch-Mode": {"navigate"},
"Sec-Fetch-Dest": {"iframe"},
"Referer": {"https://www.footlocker.pt/en/product/~/314206535404.html"},
"Accept-Language": {"en-GB,en;q=0.9"},
}
challengeResp, err := client.Do(challengeReq)
if err != nil {
log.Println(err)
return
}
defer challengeResp.Body.Close()
challengeBody, _ := io.ReadAll(challengeResp.Body)
htmlBody := string(challengeBody)
solution_url, solution, err := solveCaptcha(htmlBody)
if err != nil {
log.Println("Captcha solving failed:", err)
return
}
formData := url.Values{}
var solutionMap map[string]interface{}
err = json.Unmarshal([]byte(solution), &solutionMap)
if err != nil {
log.Println(err)
return
}
for key, value := range solutionMap {
formData.Set(key, fmt.Sprintf("%v", value))
}
encodedFormData := formData.Encode()
solutionReq, err := http.NewRequest("POST", solution_url, strings.NewReader(encodedFormData))
if err != nil {
log.Println(err)
return
}
log.Println(solution_url)
solutionReq.Header = http.Header{
"Host": {"geo.captcha-delivery.com"},
"sec-ch-ua": {`"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"`},
"sec-ch-ua-mobile": {"?0"},
"sec-ch-ua-platform": {"\"macOS\""},
"Upgrade-Insecure-Requests": {"1"},
"User-Agent": {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"},
"Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"},
"Sec-Fetch-Site": {"same-origin"},
"Sec-Fetch-Mode": {"cors"},
"Sec-Fetch-Dest": {"empty"},
"Referer": {challenge.URL},
"Accept-Language": {"en-GB,en;q=0.9"},
}
solutionResp, err := client.Do(solutionReq)
if err != nil {
log.Println(err)
return
}
defer solutionResp.Body.Close()
solutionBody, _ := io.ReadAll(solutionResp.Body)
fmt.Println(string(solutionBody))
fmt.Println(string(solutionResp.Status))
fmt.Println("Captcha solution posted, response:", string(solutionBody))
var solutionCookie CaptchaSolutionResponse
err = json.Unmarshal([]byte(solutionBody), &solutionCookie)
if err != nil {
log.Fatalf("Failed to parse captcha solution response: %v", err)
}
cookieParts := strings.Split(solutionCookie.Cookie, ";")
datadomeCookie := cookieParts[0]
// Log the cookie for verification
fmt.Printf("Extracted Cookie: %s\n", datadomeCookie)
req.Header.Set("Cookie", datadomeCookie)
req, err := http.NewRequest("POST", diocanurl, bytes.NewBuffer([]byte(data)))
if err != nil {
log.Println(err)
return
}
req.Header = headers
retryResp, err := client.Do(req)
if err != nil {
log.Println(err)
return
}
defer retryResp.Body.Close()
retryBody, _ := io.ReadAll(retryResp.Body)
fmt.Println("Page reloaded successfully", string(retryBody))
} else {
fmt.Println("Page loaded successfully", string(body))
}
}
func solveCaptcha(html string) (string, string, error) {
// Define the solve API URL
solveURL := "https://datadome.takionapi.tech/solve"
// Create the payload with the HTML string
payload := map[string]string{
"html": html,
}
// Serialize the payload to JSON
payloadBytes, err := json.Marshal(payload)
if err != nil {
return "", "", fmt.Errorf("failed to marshal payload: %v", err)
}
// Create the POST request to the captcha-solving service
req, err := http.NewRequest(http.MethodPost, solveURL, bytes.NewBuffer(payloadBytes))
if err != nil {
return "", "", fmt.Errorf("failed to create request: %v", err)
}
// Set the required headers
req.Header.Set("x-api-key", apiKey) // Replace with your actual API key
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36")
req.Header.Set("Content-Type", "application/json")
// Send the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", "", fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()
// Check if the API returns a 500 status code
if resp.StatusCode == 500 {
return "", "", errors.New("API returned status code 500")
}
// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", "", fmt.Errorf("failed to read response body: %v", err)
}
// Parse the response JSON into a struct
var solveResp CaptchaSolveResponse
if err := json.Unmarshal(body, &solveResp); err != nil {
return "", "", fmt.Errorf("failed to unmarshal response: %v", err)
}
// Check if there's an error in the API response
if solveResp.Error != "" {
return "", "", fmt.Errorf("captcha solving error: %s", solveResp.Error)
}
// Convert the RawMessage Payload to a string
var payloadString string
if err := json.Unmarshal(solveResp.Payload, &payloadString); err != nil {
// Handle case where payload is not a simple string
log.Println("Payload is not a string, trying to handle as a JSON object")
// If payload is not a string, try unmarshaling it into a more generic map
var payloadObject map[string]interface{}
if err := json.Unmarshal(solveResp.Payload, &payloadObject); err != nil {
return "", "", fmt.Errorf("failed to unmarshal payload as JSON object: %v", err)
}
// Return the entire JSON object as a string (or you can handle specific fields here)
payloadBytes, err := json.MarshalIndent(payloadObject, "", " ")
if err != nil {
return "", "", fmt.Errorf("failed to marshal payload object: %v", err)
}
return solveResp.URL, string(payloadBytes), nil
}
// Return the "url" and "payload" field
return solveResp.URL, payloadString, nil
}
Last updated