Fastly CDN (VCL)

DataDome Fastly module detects and protects against bot activity.

This documentation is for version 3.x.x

  • If you are running with version 2.x.x, please follow this documentation​.
  • If you are upgrading your integration from version 2.x.x to version 3.x.x, please follow this guide to ensure a successful migration.

DataDome Bot protection can be integrated directly inside Fastly.

Before the regular Fastly process starts, a preflight request is performed on the closest DataDome endpoint. Depending on the API response, the module either blocks the request or lets Fastly proceed with the regular process.

The module has been implemented to ensure the best user experience: if any errors were to occur during the preflight, or if the timeout is reached, the module will automatically disable its blocking mechanism and allow the regular Fastly process to proceed.

Prerequisites

🚧

You need to Contact Fastly support to enable some features to be able to use DataDome Bot Protection

Send an email :

  • To: [email protected]
  • Subject: Pragmas needed on Service XXXXXXXXXX for DataDome support
Dear Fastly Support:

In order to enable DataDome support please set these pragmas:
• fix_unsent_body_drain
• no_body_if_bereq_is_get_or_head

on the following service:
• Service XXXXXXXXXX
• Account YYYYYYYYYY

Thanks!

Installation

❗️

Before integrating DataDome, you need to ask Fastly Support to enable DataDome support for your Fastly service as described here.

DataDome provides 2 integration options:

  • A. [Recommended] VCL snippets through the Fastly dashboard
  • B. VCL snippets through Terraform

You can find the code here

A. VCL snippets through the Fastly dashboard

  1. Create a Fastly service or use an existing one, then create a new version of this service by cloning the actual one.
  1. Go to VCL snippets
  1. Download and extract our Fastly module. Fastly snippets are available inside the snippet folder. Upload them one by one :
  2. Start with the init one.
    Update your own server server key (You can find it inside your dashboard)
  1. Do the same things from all others vcl snippets : recv, miss, pass, fetch, error and deliver).

For each one, you need to select the corresponding subroutine.

At the end you should have 7 snippets like this. Double check for each one the type is correct.

  1. Click activate to deploy the new Fastly configuration.
  1. You are now protected by DataDome

B. VCL snippets through Terraform

This option provisions the same 7 VCL snippets as option A above, but manages them as code through the Fastly Terraform provider instead of uploading them manually in the dashboard.

  1. Download and extract our Fastly module. The snippet folder contains the 7 .vcl files (init, recv, miss, pass, fetch, error, deliver) that Terraform will upload.
  2. Open the init.vcl file and set your own server-side key (available in our dashboard), as you would in step 4 of option A. This is also where you can adjust any other setting listed in the Configuration section below.
  3. Create a Fastly API token by following the Fastly documentation, then export it as an environment variable in the shell you will run Terraform from:
export FASTLY_API_KEY=<your API key>
  1. Add the locals block and the dynamic "snippet" block below to the fastly_service_vcl resource of your existing Fastly service in Terraform. Point datadome_snippets_path to the folder where you extracted the snippets in step 1 (for example, if you copied the snippet folder next to your .tf files, path.module will work as-is).
provider "fastly" {}

locals {
  # Folder containing the datadome .vcl files extracted in step 1
  datadome_snippets_path = path.module

  # Name of every DataDome snippet to upload - keep as-is
  datadome_snippets = toset([
    "init",
    "recv",
    "pass",
    "fetch",
    "deliver",
    "miss",
    "error"
  ])
}

resource "fastly_service_vcl" "main" {
  # name = "datadome_protected_service"

  # ... keep the domain, backend and any other block already configured for your service ...

  # This dynamic block creates one `snippet` block
  # per DataDome snippet listed in local.datadome_snippets
  dynamic "snippet" {
    for_each = local.datadome_snippets

    content {
      type     = snippet.value
      priority = 10
      name     = format("datadome_%s", snippet.value)
      content  = file(format("%s/%s.vcl", local.datadome_snippets_path, snippet.value))
    }
  }
}
  1. Deploy your changes:
terraform init
terraform plan
terraform apply
  1. You are now protected by DataDome.

Configuration

The Fastly module provide two different table to customize the behavior of the module:

  • datadome_settings a STRING table that will contain the different settings of the module
  • datadome_patterns a REGEX table that will contain every Regular expression used in the module

datadome_settings fields

SettingDescriptionRequiredDefault value
server_side_keyYour DataDome server-side keytrue-
enable_referrer_restorationSet to true to restore original referrer when a challenge is passed.falsefalse
enable_graphql_supportSet to true to enable GraphQL extraction of operation name on POST request.falsefalse
enable_mcp_supportSet to true to enable the extraction of Model Context Protocol properties.falsefalse
enable_replay_protectionSet to true to prevent replay attack in case of Early-Data requests.falsefalse
remove_matrix_paramsIgnore per-segment matrix params from the path to evaluate exclusion regex.falsetrue

datadome_patterns fields

SettingDescriptionRequiredDefault value
static_asset_exclusionThe static -asset extension allowlist.false(?i)\.(avi|avif|bmp|css|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|json|less|map|mka|mkv|mov|mp3|mp4|mpeg|mpg|ogg|ogm|opus|otf|png|svg|svgz|swf|ttf|wav|webm|webp|woff|woff2|xml|zip)$
url_pattern_exclusionThe user-defined exclusion regex. Leave empty to disable.false-
encoded_char_inclusionRegex of encoded characters that force a static-asset match back into inspection. Leave empty to disable.false(?i)(%23|%25|%2f|%3b|%3f)
graphql_endpointThe regex that will be applied to req.url.path to detect GraphQL requests.false(?i)graphql
mcp_endpointThe regex that will be applied to req.url.path to detect MCP requests.false(?i)mcp

Advanced configuration

GraphQL support

It is possible to enable graphQL support and extract operation type and operation name from the request body.
Set the boolean value of enable_graphql_support to true in the datadome_settings table.

Once enabled, POST requests targeting a GraphQL endpoint with content-type: application/json will be analyzed to extract GraphQL operation name and type.

It is also possible to customize the GraphQL endpoint by updating the graphql_endpoint in the datadome_patterns table.

MCP support

It is possible to enable support for the Model Context Protocol and extract information from the request body.
Set the boolean value of enable_mcp_support to true in the datadome_settings table.

Once enabled, requests targeting an MCP endpoint will be analyzed to extract MCP properties according to the official specification:

  • mcp-session-id and mcp-protocol-version headers
  • jsonRpcVersion, jsonRpcRequestId, and mcpMethod request body fields
  • mcpParamsClientInfoName and mcpParamsClientInfoVersion request body fields when the method is initialize
  • mcpParamsToolName request body field when the method is tools/call

It is also possible to customize the MCP endpoint by updating the mcp_endpoint in the datadome_patterns table.

Referrer restoration

When passing a DataDome challenge on browsers other than Firefox, the referrer value is updated which can lead to inconsistent results in website analytics.

It is possible restore the Referer header to its original value for your backend:

  • Contact our support team, they will review your requirements and provide you with the best recommendations.
  • Set the boolean value of enable_referrer_restoration to true in the datadome_settings table

Configure custom data enrichment

📘

Custom data enrichment feature

DataDome let you enrich in real time our detection engine by sending us some custom fields with your business data. These fields can be used for specific detection models.

👋 Please reach out to our support team for reviewing the data received.

You can implement a vcl sub-routine called handle_custom_fields to define the values of the custom fields you need.
This subroutine will be used when calling the Bot Protect API, adding the custom values.

Below an example with the custom fields being set dynamically

sub handle_custom_fields {
    # sample values, update to your needs
    set bereq.http.x-datadome-params:customFieldString1 = req.http.x-custom-header;
    set bereq.http.x-datadome-params:customFieldString2 = "customField - String - 2";
    set bereq.http.x-datadome-params:customFieldString3 = "customField - String - 3";
    set bereq.http.x-datadome-params:customFieldInteger1 = req.http.x-custom-integer;
    set bereq.http.x-datadome-params:customFieldInteger2 = "42";
    set bereq.http.x-datadome-params:customFieldFloat1 = "3.1415";
    
    # UserID & ProductID
    set bereq.http.x-datadome-params:userID = req.http.x-user-id;
    set bereq.http.x-datadome-params:productID = req.http.x-product-id;
}

FAQ

How to log enriched headers?

Before any setup, please read our requirements about the enriched headers.

  1. Set up a real-time logging providers
  2. Edit the log format. DataDome's headers are available in the req.http object.
    1. For example req.http.x-datadome-isbot

Please find below an example with Loggly:

{
    "timestamp":"%{begin:%Y-%m-%dT%H:%M:%S}t",
    "client_ip":"%{req.http.Fastly-Client-IP}V",
    "geo_country":"%{client.geo.country_name}V",
    "geo_city":"%{client.geo.city}V",
    "url":"%{json.escape(req.url)}V",
    "request_referer":"%{json.escape(req.http.referer)}V",
    "request_user_agent":"%{json.escape(req.http.User-Agent)}V",
    "fastly_is_edge":%{if(fastly.ff.visits_this_service == 0, "true", "false")}V,
    "response_state":"%{json.escape(fastly_info.state)}V",
    "response_status":%{resp.status}V,
    "response_reason":%{if(resp.response, "%22"+json.escape(resp.response)+"%22", "null")}V,
    "response_body_size":%{resp.body_bytes_written}V,
    "request_method":"%{json.escape(req.method)}V",
    "request_protocol":"%{json.escape(req.proto)}V",
    "fastly_server":"%{json.escape(server.identity)}V",
    "host":"%{if(req.http.Fastly-Orig-Host, req.http.Fastly-Orig-Host, req.http.Host)}V",
    "datadome-isbot":"%{json.escape(req.http.x-datadome-isbot)}V",
    "datadome-botname":"%{json.escape(req.http.x-datadome-botname)}V",
    "datadome-ruletype":"%{json.escape(req.http.x-datadome-ruletype)}V",
     "datadome-captchapassed":"%{json.escape(req.http.x-datadome-captchapassed)}V"
   }

How can I avoid POST requests losing their body and failing?

You should ask Fastly Support to enable specific pragmas for your Fastly service as described here.

Does DataDome support Fastly Signal Sciences WAF?

Yes. DataDome is fully compatible with Signal Sciences WAF on Fastly.

DataDome is executed first by design, inside vcl_recv. Fastly Signal Sciences is executed later, just before the request is sent to the origin. This order is fixed by the Fastly integration architecture and cannot be reversed through VCL ordering or configuration. Therefore, Signal Sciences cannot be placed in front of DataDome to reduce the number of requests analyzed by DataDome within the same Fastly service.

How can I skip the detection for specific requests?

It is possible to skip the detection based on the parameters of incoming requests (e.g. User-Agent, URL, ACL list).

To skip DataDome's detection, you need to set the x-datadome-skip-detection header to the value of req.xid variable in a VCL snippet that runs before the vcl_recv subroutine.

# Example of condition to skip DataDome's detection
if (req.url ~ "^/path/to/exclude") {
  set req.http.x-datadome-skip-detection = req.xid;
}

Are requests in the cache protected by DataDome ?

Yes. DataDome is executed at the beginning of the request (inside vcl_recv) and before the cache lookup.

This ensures that all content is protected, but the DataDome integration does not affect your existing cache configuration or your cache hit ratio.



Did this page help you?