# Connect Reverse ETL Source to Custom Audience

<p>This guide takes you through the steps to connect a <a href="https://www.rudderstack.com/docs/sources/reverse-etl/" >Reverse ETL source</a> to your Custom Audience destination and configure field mappings.</p>

<blockquote class="success">
  <div class="tip-quote">
    
    <div class="tip-text">
      You can connect multiple Reverse ETL sources to the Custom Audience destination.
    </div>
  </div>
</blockquote>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="setup">Setup</h2><ol>
<li>Set up and configure your Reverse ETL source.</li>
<li>In the <strong>Overview</strong> tab of the source page, click <strong>Add destination</strong> &gt; <strong>Create new destination</strong>. You can also select an already-configured destination here.</li>
<li>From the list of destinations, select <strong>Custom Audience</strong> and click <strong>Continue</strong>.</li>
<li>Configure the following settings to specify how RudderStack connects to your API:</li>
</ol>
<table>
<thead>
<tr>
<th>Setting</th>
<th><div>Description</div></th>
</tr>
</thead>
<tbody>
<tr>
<td>Name</td>
<td>A unique name that identifies this destination in your workspace.</td>
</tr>
</tbody>
</table>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="audience-delivery-api-configuration">Audience delivery API configuration</h3><table>
<thead>
<tr>
<th>Setting</th>
<th><div>Description</div></th>
</tr>
</thead>
<tbody>
<tr>
<td>Enter base URL</td>
<td>Specify the root URL for your API (for example, <code>https://api.example.com</code>). All requests are sent to this URL.</td>
</tr>
<tr>
<td>Specify authentication</td>
<td>Choose how RudderStack authenticates with your API. <br /><br />See <a href="#authentication" >Authentication</a> section below for details.</td>
</tr>
<tr>
<td>Custom headers</td>
<td>This <strong>optional</strong> setting lets you add extra key-value headers to every request (for example, <code>Content-Type</code>, <code>X-API-Version</code>).</td>
</tr>
</tbody>
</table>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="authentication">Authentication</h4><table>
<thead>
<tr>
<th>Method</th>
<th><div>Description</div></th>
</tr>
</thead>
<tbody>
<tr>
<td>No authentication</td>
<td>None — use this for public endpoints only</td>
</tr>
<tr>
<td>Basic Auth</td>
<td>Enter a username and password</td>
</tr>
<tr>
<td>API Key</td>
<td>Specify a header name (for example, <code>X-API-Key</code>) and API key value</td>
</tr>
<tr>
<td>Bearer Token</td>
<td>Enter a bearer token value</td>
</tr>
</tbody>
</table>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="endpoint-configurations">Endpoint configurations</h3><p>Configure how RudderStack <strong>adds</strong>, <strong>updates</strong>, and <strong>removes</strong> audience members:</p>
<table>
<thead>
<tr>
<th>Setting</th>
<th>When it runs</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Add record</strong></td>
<td>A user enters the audience</td>
</tr>
<tr>
<td><strong>Update record</strong></td>
<td>A member&rsquo;s mapped attributes change. <br /><br />
<!DOCTYPE html>
<html lang="en">
<blockquote class="tip">
  <div class="tip-quote">
    
    <div class="tip-text">Set <strong>Use Add record configuration</strong> to <strong>Yes</strong> to reuse the same method, path, template, and fields as <strong>Add record</strong>.</div>
  </div>
</blockquote>
</html></td>
</tr>
<tr>
<td><strong>Remove record</strong></td>
<td>A user leaves the audience</td>
</tr>
</tbody>
</table>

<figure class="image--main "  >
    <a 
         href="/docs/images/audiences/destinations/custom-audience/use-add-record-configuration.webp"
        
        >
        <img src="/docs/images/audiences/destinations/custom-audience/use-add-record-configuration.webp" 
         alt="Use Add record configuration"  
         
         
        decoding="async" loading="lazy" class="img-shortcode"/>
    </a>
    
</figure>

<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="action-specific-settings">Action-specific settings</h3><p>Each action&rsquo;s (add/update/remove record) settings follow the same structure.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="http-method-and-endpoint-path">HTTP method and endpoint path</h4><table>
<thead>
<tr>
<th>Setting</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>HTTP method</td>
<td><code>POST</code>, <code>PUT</code>, <code>PATCH</code>, <code>GET</code>, or <code>DELETE</code> for this action.</td>
</tr>
<tr>
<td>Endpoint path URL</td>
<td>Path appended to the <a href="#audience-delivery-api-configuration" >base URL</a>. <br /><br />It supports simple interpolation with connection values, for example <code>/audiences/{{connection.audienceId}}/members</code>.</td>
</tr>
</tbody>
</table>

<html lang="en">
<blockquote class="info">
  <div class="tip-quote">
    
    <div class="tip-text">The resolved URL is the base URL plus the evaluated endpoint path.</div>
  </div>
</blockquote>

</html>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="batch-size">Batch size</h4><table>
<thead>
<tr>
<th>Setting</th>
<th>Description</th>
<th>Default value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Batch size</td>
<td>Maximum records per HTTP request for this action. Range <strong>1</strong>–<strong>5000</strong></td>
<td>5000</td>
</tr>
</tbody>
</table>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="request-body-template">Request body template</h4><p>Define the JSON body sent to your API. The template follows <a href="https://docs.jsonata.org/overview.html" >JSONata syntax</a>.</p>

<html lang="en">
<blockquote class="info">
  <div class="tip-quote">
    
    <div class="tip-text">Templates are validated when you save the destination. An invalid syntax is rejected with an error that identifies the action and issue.</div>
  </div>
</blockquote>

</html>
<p>A sample request body template is shown below:</p>
<div class="rs-code">
  <div class="rs-code__head">json<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-json" data-lang="json">{
  &#34;audience_id&#34;: $$.connection.audienceId,
  &#34;elements&#34;: [$$.records.{
    &#34;userIds&#34;: [
      {&#34;idType&#34;: &#34;SHA256_EMAIL&#34;, &#34;idValue&#34;: sha256_email},
      {&#34;idType&#34;: &#34;GOOGLE_AID&#34;, &#34;idValue&#34;: google_aid}
    ],
    &#34;firstName&#34;: first_name,
    &#34;lastName&#34;: last_name,
    &#34;nested&#34;: {
      &#34;org&#34;: &#34;static_value&#34;
    }
  }]
}</code></pre></div>
</div>
<p><strong>Global objects available at runtime</strong></p>
<table>
<thead>
<tr>
<th>Object</th>
<th><div>Description</div></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$$.records</code></td>
<td>Array of records in the current batch (after field processing and hashing)</td>
</tr>
<tr>
<td><code>$$.connection</code></td>
<td>Sync connection metadata, including <code>audienceId</code></td>
</tr>
</tbody>
</table>
<p><strong>Allowed operations</strong></p>
<ul>
<li>Object and array construction</li>
<li>Path expressions (<code>$$.records</code>, <code>$$.connection.audienceId</code>)</li>
<li>Iteration with <code>{ ... }</code> — for example, <code>$$.records.{ &quot;email&quot;: email, &quot;user_id&quot;: user_id }</code></li>
<li>String, number, and boolean literals</li>
<li><code>Number()</code> casting where needed, for example,<code>$number($$.connection.audienceId)</code></li>
</ul>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="template-example">Template example</h4><p>A template for generic batched members is shown below:</p>
<div class="rs-code">
  <div class="rs-code__head">json<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-json" data-lang="json">{
   &#34;audienceId&#34;: $$.connection.audienceId,
   &#34;users&#34;: [$$.records.{
    &#34;email&#34;: email,
     &#34;user_id&#34;: user_id
  }]
}</code></pre></div>
</div>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="template-fields">Template fields</h4><p>After you define a template, RudderStack extracts field names referenced under <code>$$.records</code> and lists them under <strong>Template fields</strong>. Click <strong>Refresh</strong> to re-scan the template, or <strong>Manage fields</strong> to configure each field.</p>

<figure class="image--main "  >
    <a 
         href="/docs/images/audiences/destinations/custom-audience/template-fields.webp"
        
        >
        <img src="/docs/images/audiences/destinations/custom-audience/template-fields.webp" 
         alt="Manage template fields"  
         
         
        decoding="async" loading="lazy" class="img-shortcode"/>
    </a>
    
</figure>

<table>
<thead>
<tr>
<th>Field setting</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Name</td>
<td>Destination-side field name used in the template</td>
</tr>
<tr>
<td>Required</td>
<td>If enabled, you must map the field to a warehouse column in the <a href="#map-identifiers" >Map identifiers</a> section</td>
</tr>
<tr>
<td>Static</td>
<td>If enabled, the value is a literal you provide in the <a href="#map-identifiers" >Map identifiers</a> section</td>
</tr>
<tr>
<td>Hash</td>
<td>Hash algorithm applied before the template runs. You can choose between <strong>None</strong>, <strong>SHA256</strong>, <strong>SHA512</strong>, or <strong>MD5</strong>.</td>
</tr>
</tbody>
</table>

<figure class="image--main "  >
    <a 
         href="/docs/images/audiences/destinations/custom-audience/configure-fields.webp"
        
        >
        <img src="/docs/images/audiences/destinations/custom-audience/configure-fields.webp" 
         alt="Configure template fields"  
         
         
        decoding="async" loading="lazy" class="img-shortcode"/>
    </a>
    
</figure>

<blockquote class="warning">
  <div class="tip-quote">
    
    <div class="tip-text"><p>If you rename or remove template fields after syncs exist, make sure to update mappings on each affected sync manually.</p>
<p>See <a href="#troubleshooting" >Troubleshooting</a> for more information.</p>
</div>
  </div>
</blockquote>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="request-preview">Request preview</h3><p>Use <strong>Fetch preview</strong> to build a sample HTTP request from test records.</p>

<html lang="en">
<blockquote class="info">
  <div class="tip-quote">
    
    <div class="tip-text">Preview does not send traffic to your API.</div>
  </div>
</blockquote>

</html>
<ol>
<li>Choose how many sample records to include (for example, <strong>1 record</strong>).</li>
<li>Edit sample data if needed.</li>
<li>Click <strong>Fetch preview</strong> to see the resolved method, URL, headers, and JSON body.</li>
</ol>

<figure class="image--main "  >
    <a 
         href="/docs/images/audiences/destinations/custom-audience/fetch-preview.webp"
        
        >
        <img src="/docs/images/audiences/destinations/custom-audience/fetch-preview.webp" 
         alt="Fetch preview"  
         
         
        decoding="async" loading="lazy" class="img-shortcode"/>
    </a>
    
</figure>

<ol start="5">
<li>Proceed to configure the mapping settings.</li>
</ol>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="mapping-settings">Mapping settings</h2><p>This section lists the settings to correctly map data from your Reverse ETL source to the Custom Audience destination.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="specify-target-audience">Specify target audience</h3>
<blockquote class="warning">
  <div class="tip-quote">
    
    <div class="tip-text">Create the audience on your platform before entering these values. RudderStack <strong>does not create</strong> audiences at the destination for this integration.</div>
  </div>
</blockquote>
<table>
<thead>
<tr>
<th>Setting</th>
<th><div>Description</div></th>
</tr>
</thead>
<tbody>
<tr>
<td>Audience name</td>
<td>Specify the name of the audience to sync data to</td>
</tr>
<tr>
<td>Audience ID</td>
<td>Specify the ID of the list on your platform</td>
</tr>
</tbody>
</table>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="sync-mode">Sync mode</h3><p>RudderStack supports only <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-modes/#mirror-mode" >Mirror mode</a> for this integration.</p>
<p>On the first run, the full membership is sent to the platform. Later runs send only inserts, updates, and deletes.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="map-identifiers">Map identifiers</h3><p>Map warehouse columns to each <a href="#request-body-template" >template field</a> defined in the Custom Audience destination.</p>
<p>You can map two types of fields:</p>
<ul>
<li><strong>Warehouse fields</strong>: Dropdown of warehouse columns</li>
<li><strong>Static fields</strong>: Specify literal values for fields configured as <strong>Static</strong> in the <a href="#template-fields" >destination settings</a></li>
</ul>

<figure class="image--main "  >
    <a 
         href="/docs/images/audiences/destinations/custom-audience/map-static-fields.webp"
        
        >
        <img src="/docs/images/audiences/destinations/custom-audience/map-static-fields.webp" 
         alt="Map static fields"  
         
         
        decoding="async" loading="lazy" class="img-shortcode"/>
    </a>
    
</figure>

<p>Required destination fields appear with an asterisk (*) during mapping.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="advanced-settings">Advanced settings</h3><table>
<thead>
<tr>
<th>Setting</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Automatically hash PII data</td>
<td>When set to <strong>Yes</strong>, RudderStack applies each field&rsquo;s configured hash algorithm to unhashed warehouse data. <br /><br />When set to <strong>No</strong>, data is treated as already hashed and hashing is skipped — even if a field has a <a href="#template-fields" >hash type</a> configured. <br /><br />See <a href="#data-hashing" >Data hashing</a> for more information.</td>
</tr>
</tbody>
</table>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="sync-settings">Sync settings</h2>

<p>RudderStack determines how and when to run a sync based on the <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-schedule-settings/" >sync schedule</a> you set for your Reverse ETL connection.</p>
<table>
<thead>
<tr>
<th>Schedule type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Basic</td>
<td>Run syncs at a set interval, optionally starting from a specified time (UTC). <br /><br />If a start time is set, syncs run from that time until the end of that day.</td>
</tr>
<tr>
<td>CRON</td>
<td>Run syncs based on a specified CRON expression (in UTC).</td>
</tr>
<tr>
<td>Manual</td>
<td>Run syncs manually.</td>
</tr>
</tbody>
</table>

<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="sync-observability-settings">Sync observability settings</h4>

<table>
<thead>
<tr>
<th>Setting</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Retain sync logs</td>
<td>This setting is toggled on by default and instructs RudderStack to store the sync logs in your warehouse. You can also configure the below settings: <br /><br /><table><tr><th width="20%">Setting</th><th width="80%">Description</th></tr><tr><td>Sync log retention</td><td>Specify the retention period of the <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-observability/#sync-logs" >sync logs</a> in your warehouse. <br /><br />If you set it to 1, then RudderStack deletes any sync log older than a day (in UTC time). </td></tr><tr><td>Snapshot table retention</td><td>Specify the number of <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-observability/#snapshot-table-schema" >snapshot tables</a> to retain. </td></tr></table></td>
</tr>
<tr>
<td>Retry failed records</td>
<td>This setting is toggled on by default and causes RudderStack to continually <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-observability/#retry-failed-records" >retry sending the failed records</a>.</td>
</tr>
<tr>
<td>Retain and show failures</td>
<td>This setting is toggled off by default. Turn it on to have RudderStack write the error recorded for every failed record to a <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/rudderstack-schema/#failed-records-table" >failed records table</a> in your warehouse and show it in the sync&rsquo;s <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-observability/#failed-records" >failed records view</a>. It applies from the next sync. <br /><br /><table><tr><th width="20%">Setting</th><th width="80%">Description</th></tr><tr><td>Failure retention</td><td>Number of days RudderStack keeps each sync&rsquo;s failed records in your warehouse. The default is 30 and the minimum is 7.</td></tr></table></td>
</tr>
</tbody>
</table>

<blockquote class="warning">
  <div class="tip-quote">
    
    <div class="tip-text">Storing sync logs, snapshot tables, and failed records may incur additional warehouse costs.</div>
  </div>
</blockquote>

<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="data-hashing">Data hashing</h2><p>The <a href="#advanced-settings" >Automatically hash PII data</a> setting lets you hash your user data before sending it to your API.</p>
<ul>
<li>Configure the setting to <strong>Yes</strong> if your incoming data is not pre-hashed</li>
<li>Configure this setting to <strong>No</strong> if your data is already hashed</li>
</ul>

<blockquote class="warning">
  <div class="tip-quote">
    
    <div class="tip-text"><p>Incorrect configuration of the <strong>Automatically hash PII data</strong> setting will result in failures.</p>
<ul>
<li>If the <strong>Automatically hash PII data</strong> setting is configured to <strong>Yes</strong> and your data is pre-hashed, the event will fail.</li>
<li>If the <strong>Automatically hash PII data</strong> setting is configured to <strong>No</strong> and your data is not pre-hashed, the event will fail.</li>
</ul>
</div>
  </div>
</blockquote>
<p>Such events are rejected with a clear error message, for example:</p>
<div class="rs-code">
  <div class="rs-code__head"><button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-text" data-lang="text">Hashing is disabled but the value for field EMAIL appears to be unhashed. Either enable hashing or send pre-hashed data.</code></pre></div>
</div>
<p>You will also see errors in the <strong>Events</strong> tab for cases that were previously marked as successful but resulted in no matches, helping you identify and fix data quality or configuration issues.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="troubleshooting">Troubleshooting</h2><table>
<thead>
<tr>
<th>Issue</th>
<th><div>Resolution</div></th>
</tr>
</thead>
<tbody>
<tr>
<td>Template is rejected on save</td>
<td>The syntax is outside the <a href="#request-body-template" >allowlisted operations</a> — the error message names the action and position</td>
</tr>
<tr>
<td>Unexpected payload shape</td>
<td>Use the <a href="#request-preview" >Fetch preview</a> with the same sample records and action type (<code>INSERT</code>, <code>UPDATE</code>, or <code>DELETE</code>) to verify the payload shape</td>
</tr>
<tr>
<td><code>400</code> errors for all records</td>
<td>Required fields are unmapped or the warehouse values are empty</td>
</tr>
<tr>
<td>Wrong audience on API side</td>
<td>Verify the <a href="#specify-target-audience" >Audience ID</a> and <code>$$.connection.audienceId</code> while <a href="#request-body-template" >configuring the template</a></td>
</tr>
<tr>
<td>Double-hashed identifiers</td>
<td><a href="#advanced-settings" >Automatically hash PII data</a> setting is set to <strong>Yes</strong> while your incoming warehouse data is pre-hashed — set this setting to <strong>No</strong></td>
</tr>
<tr>
<td>Sync fails after destination edit</td>
<td>Update sync mappings if destination field names or required flags have changed.<br /><br />See <a href="#template-fields" >Template fields</a> for more information</td>
</tr>
</tbody>
</table>
<br />
