Destinations

Forward conversion events to Meta, Google Ads and GA4, and events or audience membership to your own signed webhooks.

Destinations forward conversion events, and for webhooks audience membership, to the systems you run your marketing in. Every destination starts in a safe state, can be tested with a synthetic event, and exports nothing until you enable it.

In this page

  • How delivery works
  • Destination types
  • Create a destination
  • Event mapping
  • Lifecycle and test mode
  • Test sends
  • Google Ads offline conversions
  • Webhooks
  • Deliveries and what accepted means
  • Replays

How delivery works

  • Mapped events of an enabled destination are delivered in the background. Each delivery is retried with exponential backoff (2^attempts x 30 seconds, at most 1 hour; a provider's Retry-After is honored instead) for up to 8 attempts, and every outcome is listed in the delivery history.
  • Consent is checked again when a delivery is sent: an ad network (Meta Conversions API, Google Ads) receives a person only with an explicit marketing grant and no GPC signal in their consent record, whatever consentPurpose the destination stores.
  • Properties in strict privacy mode never send to ad networks. Quarantined events and redacted server events are never sent.
  • The event's own consent purpose never authorizes an export: a functional server order is collected without marketing consent, but reaches Meta or Google Ads only for a person with an explicit marketing grant.

Destination types

destinationconfig (stored and returned)credentials (write-only, encrypted at rest)
meta_capipixelId (5 to 32 digits), optional apiVersion such as v26.0, optional testEventCode (up to 64 letters, digits, _ or -)accessToken (10 to 2,048 characters)
google_ads_offlinecustomerId (123-456-7890 or 10 digits), conversionAction (customers/<id>/conversionActions/<id>), optional loginCustomerId, credentialSource: "go_connection", goConnectionIdnone: Kentro uses your Kentro GO Google Ads connection
ga4_mpmeasurementId (G- plus 4 to 20 upper-case letters or digits)apiSecret (8 to 256 characters)
webhookurl (https:// with a public host name, up to 2,048 characters)signingSecret (16 to 256 characters)

Credentials are never returned; every destination reports hasCredentials and credentialSource (stored, go_connection or null) instead, plus testMode, deliveryMode (test or live) and, in test mode, a testModeNote. In Kentro GO, the Pixel page's Destinations tab creates and edits all four types (click New destination).

Create a destination

curl -sS https://apis.kentro.live/pixel/v1/destinations \
  -H "Authorization: Bearer $KENTRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": "meta_capi",
    "name": "Meta dataset",
    "propertyId": "0192f0c4-6d1e-7a52-9c1f-3b1f2c7f9a10",
    "config": { "pixelId": "123456789012345", "testEventCode": "TEST12345" },
    "credentials": { "accessToken": "YOUR_META_ACCESS_TOKEN" },
    "eventMapping": [
      { "source": "commerce.order_paid", "target": "Purchase" },
      { "source": "marketing.lead_captured", "target": "Lead" }
    ],
    "consentPurpose": "marketing"
  }'
{
  "id": "0192f0ca-5e6f-7081-9293-a4b5c6d7e8f9",
  "propertyId": "0192f0c4-6d1e-7a52-9c1f-3b1f2c7f9a10",
  "destination": "meta_capi",
  "name": "Meta dataset",
  "status": "configured",
  "testMode": true,
  "deliveryMode": "test",
  "testModeNote": "Test mode: every delivery is sent with config.testEventCode and shows only in Events Manager > Test Events; nothing reaches the live dataset.",
  "config": { "pixelId": "123456789012345", "testEventCode": "TEST12345" },
  "eventMapping": [
    { "source": "commerce.order_paid", "target": "Purchase", "fields": {}, "enabled": true },
    { "source": "marketing.lead_captured", "target": "Lead", "fields": {}, "enabled": true }
  ],
  "consentPurpose": "marketing",
  "mappingVersion": 1,
  "lastDeliveryAt": null,
  "error": null,
  "hasCredentials": true,
  "credentialSource": "stored",
  "createdBy": "7d1f2e3a-4b5c-4d6e-8f90-1a2b3c4d5e6f",
  "createdAt": "2026-10-09T18:10:00.000Z",
  "updatedAt": "2026-10-09T18:10:00.000Z"
}
  • propertyId is optional; when set it must belong to your workspace (400 PROPERTY_NOT_FOUND).
  • consentPurpose (default marketing) is the purpose a person must have granted before anything about them is exported. GA4 and webhook destinations follow it; ad networks always require an explicit marketing grant and no GPC signal.
  • testMode is ignored on creation: every destination starts with status: "configured" and testMode: true.

Event mapping

eventMapping is a list of up to 50 entries { "source", "target", "enabled" }, each with an optional fields object of up to 50 string pairs (webhooks use it to fill data). source must be a builtin or an active custom event of your workspace, and neither a reserved event nor the deprecated marketing.product_viewed. A mapping saved earlier on marketing.product_viewed keeps matching commerce.product_viewed events. Duplicates of the same source and target are refused; enabled defaults to true.

DestinationValid targets
meta_capiA Meta standard event (Purchase, Lead, CompleteRegistration, AddToCart, InitiateCheckout, ViewContent, Search, Subscribe, Contact, Schedule) or a custom event name: a letter, then letters, digits or underscores, at most 50 characters.
ga4_mpA Measurement Protocol event name: a letter, then letters, digits or underscores, at most 40 characters, not a reserved GA4 name, app-only name or reserved prefix.
google_ads_offlineA conversion action resource name, customers/<id>/conversionActions/<id>.
webhook1 to 100 letters, digits, dots, colons, dashes or underscores.

Invalid mappings answer 400 EVENT_MAPPING_INVALID with an errors array. Changing the mapping with PATCH increments mappingVersion.

Lifecycle and test mode

StatusMeaning
configuredCreated, exports nothing. Test sends are allowed.
enabledExports mapped events.
disabledSwitched off by you.
errorSet by Kentro when delivery fails; not settable through the API.
  • Enable with PATCH /v1/destinations/:id and { "status": "enabled" }. Enabling requires stored credentials (or a linked Kentro GO connection), otherwise 409 CREDENTIALS_REQUIRED, and at least one enabled mapping, otherwise 409 EVENT_MAPPING_REQUIRED. A meta_capi destination in test mode also needs config.testEventCode, otherwise 400 TEST_EVENT_CODE_REQUIRED.
  • PATCH also updates name, propertyId, config, credentials (null clears the stored secret), eventMapping, consentPurpose and testMode. DELETE /v1/destinations/:id removes the destination and keeps its delivery history.
  • testMode starts true; PATCH with { "testMode": false } turns it off. While it is on, an enabled destination delivers only as tests: Meta deliveries carry config.testEventCode and appear only in Events Manager's Test Events, GA4 deliveries go to the validation endpoint and Google Ads uploads run with validateOnly (both record nothing), and webhook requests carry "test": true and an X-Kentro-Test: 1 header.

Test sends

POST /v1/destinations/:id/test sends one synthetic event through the real provider API and records the outcome as a delivery row. The synthetic identity is a hashed constant, never a real person.

DestinationWhat the test does
meta_capiSends one event (the first enabled mapping's target, else Lead) with the testEventCode from the body, so it shows only under Test Events. Without { "testEventCode": "..." } the test answers 400 TEST_EVENT_CODE_REQUIRED.
ga4_mpSends one event to the Measurement Protocol validation endpoint and returns its validation messages. The delivery row is sent with validateOnly: true (or rejected).
google_ads_offlineUploads one click conversion with validateOnly: true using an access token from your Kentro GO Google Ads connection.
webhookPOSTs a signed synthetic payload (kentro.pixel.destination_test) with a 5 second timeout; any 2xx answer counts as accepted.
curl -sS https://apis.kentro.live/pixel/v1/destinations/$DESTINATION_ID/test \
  -H "Authorization: Bearer $KENTRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "testEventCode": "TEST12345" }'
{
  "ok": true,
  "status": "accepted",
  "validateOnly": false,
  "deliveryId": "0192f0cb-6f70-7192-a3b4-c5d6e7f8a9b0",
  "destination": "meta_capi",
  "testMode": true,
  "deliveryMode": "test",
  "providerResponse": { "eventsReceived": 1, "messages": [], "fbtraceId": "A1b2C3d4E5f6" },
  "error": null,
  "note": "Provider acceptance means a well-formed test request was accepted. It is not a matched or attributed conversion."
}
ErrorWhen
409 CREDENTIALS_REQUIREDNo stored secret.
400 TEST_EVENT_CODE_REQUIREDMeta test without a test event code.
409 GO_CONNECTION_NOT_FOUNDThe Kentro GO connection does not exist or is disconnected.
409 GO_CONNECTION_NOT_USABLEThe connection cannot issue an access token: reconnect the provider in Kentro GO.
502 GO_PROVIDER_UNAVAILABLE, 502 GO_API_ERRORGoogle could not refresh the access token right now, or the Kentro GO call failed.
503 GO_API_UNAVAILABLEGoogle Ads sending through Kentro GO is not configured on this environment.
422 WEBHOOK_TARGET_NOT_ALLOWEDThe webhook URL fails the rules below.
503 CREDENTIALS_UNAVAILABLEStored credentials cannot be opened.

Google Ads offline conversions

  • Set credentialSource: "go_connection" and the connection's id in goConnectionId (connections are listed by GET https://apis.kentro.live/go/v1/connections). No Google Ads developer token is needed: Kentro sends conversions with the OAuth access token of your connection and never stores Google tokens on the destination.
  • conversionAction is the resource name of an upload-type conversion action; loginCustomerId is the manager account id when the connection signs in through one.
  • Mapped conversions are uploaded as click conversions and need a Google click id (gclid, gbraid or wbraid) captured on a visit with marketing consent; without one the delivery is suppressed (no_click_id).
  • Refunds and cancellations are sent as adjustments: a retraction for a cancellation or a full refund, a restatement of the remaining value for a partial refund. The consent signals on each upload (adUserData, adPersonalization) come from the person's consent record.

Webhooks

Webhook URLs must use https:// and a public host name: no IP literals, localhost, .local or .internal names, no credentials and no fragment. At send time every address the host resolves to must be public, and redirects are not followed. Live deliveries time out after 10 seconds (test sends after 5). A 2xx answer counts as accepted; 408, 429, 5xx and network errors are retried; any other answer is recorded as rejected. Kentro reads at most the first 4 KB of your response.

HeaderValue
Content-Typeapplication/json
User-AgentKentro-Pixel-Webhook/1.0
X-Kentro-Eventkentro.pixel.event, kentro.pixel.audience_membership or kentro.pixel.destination_test
X-Kentro-DeliveryLive deliveries: the delivery id, the same on every retry (use it to drop repeats)
X-Kentro-TestLive deliveries of a destination in test mode: 1
X-Kentro-TimestampUnix time in seconds
X-Kentro-Signaturesha256= followed by the hex HMAC-SHA256 of timestamp + "." + rawBody, keyed with your signingSecret

Payload types:

  • kentro.pixel.event (one mapped event): type, id (the delivery id), created_at, destination_id, test, targets, event and, when the matched mapping has fields, data. event carries event_id, event_name, occurred_at, received_at, source_type, authoritative, refs, context (page_path, referrer_domain, utm, device_class), the effective consent and properties. data holds one value per fields entry, read from a dotted path of event: { "order": "refs.order_ref" } sends "data": { "order": "<the order ref>" }.
  • kentro.pixel.audience_membership (one page of an audience sync): type, id, created_at, destination_id, test, audience (id, name), mode, window (from, to), page, page_size, and joined and left lists of { "subject_id", "subject_type", "subject_key" }; a leave caused by a privacy deletion carries "deleted": true.
  • kentro.pixel.destination_test (a test send):
{
  "type": "kentro.pixel.destination_test",
  "id": "0192f0cb-6f70-7192-a3b4-c5d6e7f8a9b0",
  "created_at": "2026-10-09T18:20:00.000Z",
  "destination_id": "0192f0ca-5e6f-7081-9293-a4b5c6d7e8f9",
  "test": true,
  "event": { "event_name": "kentro.test", "event_id": "kentro_test_0192f0cb-6f70-7192-a3b4-c5d6e7f8a9b0", "occurred_at": "2026-10-09T18:20:00.000Z", "properties": { "synthetic": true } }
}

Verify the signature over the raw request bytes before parsing the JSON, and reject timestamps outside a window you choose (Kentro uses 300 seconds for its own signatures):

const crypto = require('node:crypto');
const http = require('node:http');

const SIGNING_SECRET = process.env.KENTRO_WEBHOOK_SECRET;
const TOLERANCE_SECONDS = 300;

function verifyKentroSignature(rawBody, headers, secret) {
  const timestamp = headers['x-kentro-timestamp'];
  const signature = headers['x-kentro-signature'] || '';
  if (!/^\d+$/.test(timestamp || '')) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > TOLERANCE_SECONDS) return false;
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex');
  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

http.createServer((req, res) => {
  const chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const rawBody = Buffer.concat(chunks);
    if (!verifyKentroSignature(rawBody, req.headers, SIGNING_SECRET)) {
      res.writeHead(401).end();
      return;
    }
    const payload = JSON.parse(rawBody.toString('utf8'));
    console.log('Kentro webhook', req.headers['x-kentro-event'], payload.id);
    res.writeHead(204).end();
  });
}).listen(8080);

Only webhook destinations can receive audience membership; an audience sync aimed at a Meta, Google Ads or GA4 destination is refused with 409 DESTINATION_CAPABILITY.

Deliveries and what accepted means

GET /v1/destinations/:id/deliveries?status&limit&cursor lists delivery rows, newest first, as { "items": [...], "nextCursor": ... }. Each row carries id, destinationId, journalId, conversionId, dedupeKey, status, testMode, validateOnly, attempts, requestRedacted, response, providerReceipt, suppressedReason, sentAt, nextAttemptAt, error and createdAt.

StatusMeaning
queuedWaiting to be sent.
sentThe request left Kentro, but the provider does not confirm acceptance: GA4 and validate-only test calls.
acceptedThe provider accepted the request.
rejectedThe provider refused it; see error and response.
failedKentro could not complete the delivery; see error.
suppressedKentro deliberately did not send it. suppressedReason: consent_not_granted, gpc_opt_out, strict_privacy_mode, quarantined, sensitive_event, no_click_id, no_client_id, privacy_request or consent_withdrawn.

accepted means the provider accepted a well-formed request. It does not mean the provider matched the event to a person, counted a conversion or attributed it to a campaign; test events never appear in live reports.

Replays

POST /v1/destinations/:id/sync with an optional { "from", "to" } window queues a replay of the window's events for an enabled destination and answers 202 with a jobId.

  • Default window: the last 7 days. At most 180 days long and starting at most 30 days back; YYYY-MM-DD values are whole UTC days with an inclusive end.
  • A destination that is not enabled answers 409 DESTINATION_NOT_ENABLED.
  • Replay recognizes events it already delivered by their delivery history, which is kept for 30 days, so a from older than that answers 400 REPLAY_RANGE_TOO_OLD.