SDK integration for account update

DataDome Account Protect detects account takeover threats and protects you against them

Account Protect can be integrated into your backend through SDK packages available on multiple platforms.

📘

Prerequisites for Account Protect

Account Protect is separate from Bot Protect and is not available on your account by default.
Please contact your account manager to enable it.

This service requires a dedicated API key, which will be available on your dashboard once it is enabled.

Main concepts

DataDome Account Protect monitors all profile update attempts and evaluates their legitimacy in real time. By analyzing behavioral and contextual signals, DataDome can recommend whether to allow or block the update—helping prevent unauthorized changes to sensitive account information.

Overview of the implementation flow for an account update.

Overview of the implementation flow for an account update.

Installation

The Account Protect SDK is distributed on multiple platforms:

You can use one of the commands below to install the relevant package for your application:

npm i @datadome/fraud-sdk-node
dotnet add package DataDome.AspNetCore.Fraud.SDK
<!-- insert in the pom.xml file of the project -->
<dependency>
  <groupId>co.datadome.fraud</groupId>
  <artifactId>fraud-sdk-java</artifactId>
  <!-- <version>1.0.1</version> --> <!-- compatible with Spring Boot 2.x -->
  <version>2.3.0</version>          <!-- compatible with Spring Boot 3.x -->
</dependency>
libraryDependencies += "co.datadome.fraud" % "fraud-sdk-java" % "2.2.1"
pip install datadome-fraud-sdk-python
composer require datadome/fraud-sdk-symfony
# 1. add `datadome/fraud-sdk-laravel` to your project
composer require datadome/fraud-sdk-laravel
# 2. Generate an autoloader
composer dump-autoload
# 3. Edit `config/app.php` to add `DataDomeServiceProvider`
# config/app.php
use DataDome\FraudSdkLaravel\Providers\DataDomeServiceProvider;
[...]
 'providers' => ServiceProvider::defaultProviders()->merge([
 [...]
 DataDomeServiceProvider::class
 
# 4. publish `datadome.php` in the `config` folder
php artisan vendor:publish
gem install datadome_fraud_sdk_ruby
go get github.com/datadome/fraud-sdk-go-package

Usage

Using the Account Protect SDK requires changes in your application to send signals regarding account updates and handle the recommendations provided by DataDome's Account Protect API.

Example for an Account Update event

package main

import (
  "log"
  "net/http"
  "time"

  dd "github.com/datadome/fraud-sdk-go"
)

func accountHandler(client *dd.Client) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    if r.Method == http.MethodPut {
      // Address field
      name := "Élysée Palace"
      line1 := "55 Rue du Faubourg Saint-Honoré"
      line2 := "2nd floor"
      city := "Paris"
      countryCode := "FR"
      regionCode := "75"
      zipCode := "75008"
      userAddress := dd.Address{
        City:        &city,
        CountryCode: &countryCode,
        Line1:       &line1,
        Line2:       &line2,
        Name:        &name,
        RegionCode:  &regionCode,
        ZipCode:     &zipCode,
      }

      // Authentication field
      authenticationMode := dd.Password
      authenticationSocialProvider := dd.Google
      authenticationType := dd.Social
      authentication := dd.Authentication{
        Mode:           &authenticationMode,
        SocialProvider: &authenticationSocialProvider,
        Type:           &authenticationType,
      }

      // User field
      createdAt := time.Now().Format(time.RFC3339)
      firstName := "Data"
      lastName := "Dome"
      title := "mr"
      phone := "+33978787878"
      email := "[email protected]"
      paymentMethodUpdated := true
      displayName := "DataDome 🤖"
      description := "Some Description / Некоторое описание"
      user := dd.User{
        ID:          "fake_user_id",
				Address:     &userAddress,
				CreatedAt:   &createdAt,
				Email:       &email,
				FirstName:   &firstName,
				LastName:    &lastName,
				Phone:       &phone,
				Title:       &title,
				DisplayName: &displayName,
				Description: &description,
				ExternalURLs: &[]string{
					"https://example.org/external1",
					"https://example.org/external2",
					"https://example.org/external3",
				},
				PictureURLs: &[]string{
					"https://example.org/image1.png",
					"https://example.org/image2.png",
					"https://example.org/image3.png",
				},
				PaymentMethodUpdated: &paymentMethodUpdated,
      }

      // Session field
      sessionId := "fake_session_id"
      session := dd.Session{
        ID:        &sessionId,
        CreatedAt: &createdAt,
      }

      validate, err := client.Validate(r, dd.NewAccountUpdateEvent(
        email,
        dd.AccountUpdateWithAuthentication(authentication),
        dd.AccountUpdateWithUser(user),
        dd.AccountUpdateWithSession(session),
      ))
      if err != nil {
        log.Printf("error during validation: %v\n", err)
      }
      if validate.Action == dd.Allow {
        w.WriteHeader(http.StatusOK)
        return
      } else {
        // Business Logic here
        // MFA
        // Challenge
        // Notification email
        // temporarly lock account
        http.Error(w, "failed", http.StatusForbidden)
        return
      }
    } else {
      http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
  }
}

func main() {
  client, _ := dd.NewClient("FRAUD_API_KEY")

  mux := http.NewServeMux()
  mux.HandleFunc("/account", accountHandler(client))

  _ = http.ListenAndServe(":8080", mux)
}
const datadomeClient = new DataDome("FraudAPIKey");

app.put('/account', async function (req, res) {
  const emailAccount = req.body.email;
  const session = { id: 'sessionId', createdAt: new Date() };
  
  const userAddress = {
    name: 'HQ',
    line1: '22 rue de la Michodiere',
    line2: '2nd floor',
    city: 'Paris',
    countryCode: 'FR',
    country: 'France',
    regionCode: '75',
    zipCode: '75002'
  };
  
  const user = {
    id: 'userId',
    title: 'mrs',
    firstName: 'Data',
    lastName: 'Dome',
    createdAt: new Date(),
    phone: '+33978787878',
    email: emailAccount,
    address: userAddress,
    displayName: 'DataDome 🤖',
    description: 'Some Description',
    externalUrls: [
    	'https://example.org/external1',
      'https://example.org/external2',
      'https://example.org/external3',
    ],
    pictureUrls: [
      'https://example.org/image1.png',
      'https://example.org/image2.png',
      'https://example.org/image3.png',
    ],
  };
  
  const authentication: Authentication = {
    socialProvider: 'google',
    type: 'social',
  	mode: 'password',
  };

  const accountUpdateEvent = new AccountUpdateEvent({
  	account: emailAccount,
	  authentication,
  	session,
	  user,
  });
  
  const datadomeResponse = await datadomeClient.validate(req, accountUpdateEvent);

  if (datadomeResponse?.action == ResponseAction.ALLOW) {
    // Process update of the account
    res.status(200).send(`Successfully updated user ${req.body.email}`);
  } else {
    res.status(401).send(`Access denied [by DD] ! ${datadomeResponse?.reasons[0]}`);
  }
});

// 5. Use it in the registration endpoint of registration controller
@PutMapping(path = "/account")
public ResponseEntity<?> accountUpdate(ServletRequest request, User user) {
  User.Builder userBuilder = co.datadome.fraud.model.User.newBuilder();
        userBuilder.id("user_id")
          .title(Title.MR)
          .email(userform.user.get("email"))
          .description("Some Description / Некоторое описание")
          .displayName("DataDome \uD83E\uDD16")
          .firstName("Data")
          .lastName("Dome")
          .phone("+33978787878")
          .paymentMethodUpdated(true)
          .pictureUrls(new String[]{"https://example.org/image1.png",
                 "https://example.org/image2.png",
                 "https://example.org/image3.png"})
          .externalUrls(new String[]{"https://example.org/external1",
                 "https://example.org/external2",
                 "https://example.org/external3"})
          .address(Address.newBuilder()
                 	 .name("Élysée Palace")
                   .line1("55 Rue du Faubourg Saint-Honoré")
                   .line2("2nd floor")
                   .city("Paris")
                   .countryCode("FR")
                   .country("France")
                   .regionCode("75")
                   .zipCode("75008")
                   .build())
          ;
	DataDomeResponse result = this.dataDomeFraudService.validate(request,
                AccountUpdateEvent.newBuilder(userBuilder.build())
                        .account(userform.user.get("email"))
                        .authentication(
                                Authentication.newBuilder()
                                        .socialProvider(SocialProvider.GOOGLE)
                                        .mode(AuthenticationMode.PASSWORD)
                                        .type(AuthenticationType.SOCIAL)
                                        .build())
                        .session(Session.newBuilder()
                                .id("session_id")
                                .build())
                        .build()
             );

			logger.info(result);
  		if (result.isAllowed()) {
        return new ResponseEntity<>(HttpStatus.OK);
      } else {
        return new ResponseEntity<>(HttpStatus.FORBIDDEN);
      }
}
# Build the user object
user = DataDomeUser.new(
  id: "user_id",
  title: DataDomeTitle::MR,
  email: params[:user][:email],
  description: "Some Description / Некоторое описание",
  display_name: "DataDome 🤖",
  first_name: "Data",
  last_name: "Dome",
  phone: "+33978787878",
  payment_method_updated: true,
  picture_urls: [
        "https://example.org/image1.png",
        "https://example.org/image2.png",
        "https://example.org/image3.png"
      ],
  external_urls: [
        "https://example.org/external1",
        "https://example.org/external2",
        "https://example.org/external3"
      ],
  address: DataDomeAddress.new(
        name: "Élysée Palace",
        line1: "55 Rue du Faubourg Saint-Honoré",
        line2: "2nd floor",
        city: "Paris",
        country_code: "FR",
        region_code: "75",
        zip_code: "75008"
      )
)

# Create authentication object
authentication = DataDomeAuthentication.new(
  social_provider: DataDomeSocialProvider::GOOGLE,
  mode: DataDomeAuthenticationMode::PASSWORD,
  type: DataDomeAuthenticationType::SOCIAL
)

# Create session object
session = DataDomeSession.new(id: "session_id")

# Create the account update event
event = DataDomeAccountUpdateEvent.new(
  account: params[:user][:email],
  user: user,
  authentication: authentication,
  session: session
)

# Build request headers from HTTP request
datadome_headers = DataDomeHeaders.new
datadome_headers.build_from_http_request(request)
datadome_request = DataDomeRequest.new(datadome_headers)

# Validate with DataDome
result = @datadome.validate(request: datadome_request, event: event)

Rails.logger.info("DataDome Response: #{result}")

if result.action == DataDomeResponseAction::ALLOW
  render json: { message: "Account updated successfully" }, status: :ok
else
  render json: { 
        error: "Access denied", 
        action: result.action,
        score: result.score,
        event_id: result.event_id
      }, status: :forbidden
end

[Route("AccountUpdate")]
[HttpPost]
public async Task<IActionResult> AccountUpdate([FromForm] AccountUpdate account)
{
  if (_authenticationService.ValidateAccountUpdate(account))
  {
  
    var session = new Session()
    {
        Id = Guid.NewGuid().ToString("D"),
        CreatedAt = DateTime.UtcNow,
    };

    var user = new User()
    {
        Id = account.Email,
        FirstName = account.Name,
        DisplayName = account.Name,
        Description = "User description",
        PictureUrls = new[] { "https://example.com/profile.jpg" },
        ExternalUrls = new[] { "https://example.com" },
        PaymentMethodUpdated = false,
    };
    
    var AccountUpdateEvent = new AccountUpdateEvent(account.Email, session, user);
    var ddResponse = await _dataDome.Validate(Request, AccountUpdateEvent);

    if (ddResponse.ResponseAction == ResponseAction.Allow)
    {
        _authenticationService.AccountUpdate(account);
        TempData["SuccessMessage"] = "Account updated successfully!";
    }
    else
    {
        // Business logic here
    }
  } else {
    await _dataDome.Collect(
        Request,
        new AccountUpdateEvent(
            account.Email,
            new Session()
            {
                Id = Guid.NewGuid().ToString("D"),
                CreatedAt = DateTime.UtcNow,
            },
            new User()
            {
                Id = account.Email,
                FirstName = account.Name,
                DisplayName = account.DisplayName,
                Description = "User description",
                PictureUrls = new[] { "https://example.com/profile.jpg" },
                ExternalUrls = new[] { "https://example.com" },
                PaymentMethodUpdated = false,
            },
            new Authentication()
            {
                Mode = AuthenticationMode.Password,
                SocialProvider = AuthenticationSocialProvider.Google,
                Type = AuthenticationType.Local
            }
        )
    );
    TempData["ErrorMessage"] = "User not found or incorrect password.";
   }

API Reference

AccountUpdateEvent

The SDK exposes methods to validate account updates that require a AccountUpdateEvent instance to be sent to the Account Protect API along with the client request itself.

Properties available for this event type are listed below:

Name

Description

Default value

Possible values

Optional

account

The unique account identifier used for the login attempt

Any string value.

No

authentication.mode

Authentication mode

biometric, mail mfa, otp, password, other

Yes

authentication.socialProvider

Authentication social provider

amazon, apple, facebook, github, google, linkedin, microsoft, twitter, yahoo, other

Yes

authentication.type

Authentication type

local, socialProvider, other

Yes

session.createdAt

Creation date of the session

Format ISO 8601 YYYY-MM-DDThh:mm:ssTZD

Yes

session.id

A unique session identifier from your system

Any string value.

Yes

user.address.city

City of the address

Any string value.

Yes

user.address.countryCode

Country of the address

Format ISO-3166-1-alpha-2

Yes

user.address.line1

Line 1 of the address

Any string value.

Yes

user.address.line2

Line 2 of the address

Any string value.

Yes

user.address.name

Name of the address

Any string value.

Yes

user.address.regionCode

Region code

Yes

user.address.zipCode

Zip code

Yes

user.createdAt

Creation date of the user

Format ISO 8601 YYYY-MM-DDThh:mm:ssTZD

Yes

user.description

Description or biography of the user

Any string value.

Yes

user.displayName

Display name of the user

Any string value.

Yes

user.email

Email of the user

Valid email address

Yes

user.externalUrls

External URLs of the user

An array of valid URL address (max 10 items)

Yes

user.firstName

First name of the user

Any string value.

Yes

user.id

A unique customer identifier from your system. It has to be the same for all other event sent

Any string value.

No

user.lastName

Last name of the user

Any string value.

Yes

user.paymentMethodUpdated

Payment method updated

A boolean value

Yes

user.phone

Phone of the user

E.164 format including + and a region code
Example : example +33978787878

Yes

user.pictureUrls

Pictures of the user

An array of valid URL address (max 10 items)

Yes

user.title

Title of the user

mr, mrs, mx

Yes

Validation response

Validating an account update event should result in a response that can include the following properties:

Name

Description

Possible Values

Always Defined

action

The recommended action to perform on the login attempt.

allow, deny

Yes

errors

A list of objects representing each error with details.
Each object will have the properties listed below.

errors[i].error

A short description of the error.

errors[i].field

The name of the value that triggered the error.

eventId

Event identifier associated to this validate event.

A valid UUID.

Yes

message

A description of the error if the status is failure or timeout.

Invalid header / Request timed out...

reasons

A list of reasons to support the recommended action.

List of reasons (Any string value.)

status

The status of the request to the Account Protect API.

ok, failure, timeout

Yes

score

The level of confidence when identifying a request as coming from a fraudster.
Only available in Ruby SDK 2.1.0+, Go SDK v1.1.0+, Node.Js SDK 2.0.0+, Java SDK 2.3.0+

Integer

Options

Options can be applied to the SDK during its instantiation.

Option Name

Description

Default Value

endpoint

The endpoint to call for the Account Protect API.

https://account-api.datadome.co

timeout

A timeout threshold in milliseconds.
When an API request times out, the SDK will allow it by default.

1500

You can find usage examples for each platform below:

client, err := dd.NewClient(
  "FRAUD_API_KEY",
  dd.ClientWithEndpoint("account-api.datadome.co"),
  dd.ClientWithTimeout(1500),
)

FAQ

What happens if there is a timeout on API request?

The SDK has been designed to have minimal impact on the user experience. If the configured timeout is reached, the SDK will cancel its pending operation and allow the application to proceed.

What happens if the API returns an error?

Errors and timeouts are handled the same way by the SDK: it will not interrupt the application and allow it to proceed.

What happens if my API key is incorrect?

Invalid keys are detected when calling the account protect API. The SDK will return an allow response to avoid blocking any login or registration attempt on the application. This response will also have a failure status and a message that describes the problem.