Currency

Pixels

A merchant pixels record stores the per-merchant configuration for ad-network tracking pixels and server-side conversion APIs (CAPI). One row per merchant. The shape mirrors what Storlaunch's storefront snippet needs to fire client-side tracking, plus the secrets needed for Ripllo to make server-side conversion calls on the merchant's behalf.

This resource has two access modes you need to keep straight:

  • GET / and PATCH / — auth-required. Returns the full record including CAPI access tokens. Used by the dashboard.
  • GET /public/:accountId — no auth. Returns IDs only, never secrets. Used by Storlaunch's storefront pages to fire client-side pixel snippets.

The explicit select list on the public route is the primary guard against shipping metaCapiAccessToken to the storefront. Any future edit to this resource must be paired-reviewed with the same rigor as a payout webhook secret.

Endpoints

Method Path Purpose
GET /api/v1/pixels Get pixel config
PATCH /api/v1/pixels Update pixel config
GET /api/v1/pixels/public/:accountId Public pixel config (no auth, no secrets)

Get pixel config

GET /api/v1/pixels

Returns the merchant's full pixel record, including secrets. If the merchant hasn't configured anything yet, returns a default stub with enabled: true and every provider field null, so the dashboard can render the empty state without special-casing.

Response

{
  "data": {
    "id": "mxp_01HX...",
    "accountId": "acc_01HX...",
    "metaPixelId": "1234567890",
    "metaCapiAccessToken": "EAAGm…",
    "metaTestEventCode": "TEST12345",
    "googleAnalyticsId": "G-AB12CD34EF",
    "googleAdsConversionId": "AW-1234567890",
    "googleAdsPurchaseLabel": "abcDEF1234",
    "tiktokPixelId": "C4ABC123…",
    "enabled": true,
    "createdAt": "2026-05-01T10:42:00.000Z",
    "updatedAt": "2026-05-13T10:11:00.000Z"
  },
  "error": null,
  "meta": { "requestId": "...", "timestamp": "..." }
}
Status error.code When
403 NO_ACCOUNT Caller's token has no accountId.

Update pixel config

PATCH /api/v1/pixels

Upserts the merchant's record. Only fields you supply are updated; omitted fields stay as-is. To clear a field, send null.

Request body

Field Type Notes
metaPixelId string (≤64) | null The Meta Pixel ID, e.g. 1234567890. Safe to embed client-side.
metaCapiAccessToken string (≤500) | null The CAPI access token, used server-side by Ripllo to send conversion events to Meta's Conversions API. Never returned by the public endpoint.
metaTestEventCode string (≤64) | null Meta's test-event code for CAPI dry runs. Set during integration testing; clear once verified.
googleAnalyticsId string (≤64) | null Measurement ID, format G-XXXXXXXX.
googleAdsConversionId string (≤64) | null Google Ads conversion ID, format AW-XXXXXXXXXX.
googleAdsPurchaseLabel string (≤64) | null Google Ads conversion label for purchases.
tiktokPixelId string (≤64) | null TikTok Pixel ID.
enabled boolean Master switch — when false, the public endpoint returns null and Ripllo's server-side CAPI dispatcher skips this merchant.

Response

The full updated MerchantPixels object (including secrets, since this is the merchant-facing endpoint).

Examples

// Node
await ripllo.pixels.update({
  metaPixelId: '1234567890',
  metaCapiAccessToken: process.env.META_CAPI_TOKEN,
  googleAnalyticsId: 'G-AB12CD34EF',
  enabled: true,
});
# Python
ripllo.pixels.update(
    meta_pixel_id='1234567890',
    meta_capi_access_token=os.environ['META_CAPI_TOKEN'],
    google_analytics_id='G-AB12CD34EF',
    enabled=True,
)
// Go
_, err := client.Pixels.Update(ctx, &ripllo.PixelsUpdateParams{
    MetaPixelID:          ripllo.String("1234567890"),
    MetaCapiAccessToken:  ripllo.String(os.Getenv("META_CAPI_TOKEN")),
    GoogleAnalyticsID:    ripllo.String("G-AB12CD34EF"),
    Enabled:              ripllo.Bool(true),
})
# curl
ripllo_curl PATCH '/api/v1/pixels' \
  '{"metaPixelId":"1234567890","enabled":true}'

Public pixel config

GET /api/v1/pixels/public/:accountId

No auth. Returns a sanitized view of the merchant's pixel IDs, suitable for shipping to a storefront page. Returns null (not 404) when:

  • The merchant has no config row.
  • The merchant has set enabled: false.
  • The merchant has a row but all four of metaPixelId, googleAnalyticsId, googleAdsConversionId and tiktokPixelId are empty. Only those four count towards "has something to fire" — googleAdsPurchaseLabel is excluded from the test, so a merchant whose only configured field is the purchase label also gets null.

This null-pass-through is what lets storefronts unconditionally request the snippet and skip rendering when it comes back falsy.

Response — success

{
  "data": {
    "metaPixelId": "1234567890",
    "googleAnalyticsId": "G-AB12CD34EF",
    "googleAdsConversionId": "AW-1234567890",
    "googleAdsPurchaseLabel": "abcDEF1234",
    "tiktokPixelId": "C4ABC123…",
    "enabled": true
  },
  "error": null,
  "meta": { "requestId": "...", "timestamp": "..." }
}

Response — nothing to fire

{ "data": null, "error": null, "meta": { ... } }

Note what's missing from the success response: metaCapiAccessToken and metaTestEventCode. These are server-side secrets and are never returned from /public/:accountId regardless of enabled state. If you see them leaking in any future release, file a security ticket immediately.

The merchant pixels object

Field Type Nullable Notes
id string no Opaque cuid — no type prefix.
accountId string no Unique — one row per merchant.
metaPixelId string yes Public, embed-safe.
metaCapiAccessToken string yes Secret. Server-side only.
metaTestEventCode string yes Optional; only set during CAPI integration testing.
googleAnalyticsId string yes Public.
googleAdsConversionId string yes Public.
googleAdsPurchaseLabel string yes Public.
tiktokPixelId string yes Public.
enabled boolean no Master switch.
createdAt, updatedAt ISO 8601 no

CAPI helper: hashEmail

Meta's CAPI expects em as the SHA-256 hex digest of email.trim().toLowerCase(). Ripllo has a hashEmail helper for this, but it lives server-side in the Ripllo backend only (backend/src/routes/pixels.ts) — the Node, Python and Go SDKs do not export it, so import { hashEmail } from '@forjio/ripllo-node' fails. It's a one-liner in every language:

// Node
import { createHash } from 'node:crypto';
const hashEmail = (email) => createHash('sha256').update(email.trim().toLowerCase()).digest('hex');
const em = hashEmail('Alice@Example.com'); // → "5a13d2..." (hex)
# Python
import hashlib
def hash_email(email: str) -> str:
    return hashlib.sha256(email.strip().lower().encode()).hexdigest()

Getting the normalisation wrong (forgetting the lowercase or the trim) is the most common reason CAPI events don't match — hash exactly those two transforms and nothing else.

Security review checklist

When changing this resource (route, schema, or the SDK surface), confirm:

  • The /public/:accountId select list explicitly lists only the public IDs — never spreads ...row.
  • metaCapiAccessToken and metaTestEventCode are not on the public select list.
  • Any new secret field added here is added to both the merchant-facing select (so it surfaces) and the public-facing select-exclusion list (so it doesn't leak).
  • There is no regression test for this. No pixels.test.ts exists; the hand-maintained select in backend/src/routes/pixels.ts is the only thing keeping metaCapiAccessToken off the public response. Review the select list by eye on every change to this route — and writing that test is a standing to-do.

This resource is high-blast-radius: a leak of metaCapiAccessToken lets anyone with the storefront URL impersonate the merchant when posting conversions to Meta, which corrupts attribution and can get the merchant's Meta account flagged.

Events

The pixels resource doesn't emit outbox events — treating credential changes as broadcastable events would be its own leak.

Nor are they audited: PATCH /pixels performs the upsert and nothing else. The audit log currently covers API-key and webhook-endpoint mutations only, so a changed metaCapiAccessToken leaves no trail. Log the change on your side if you need one.

Next

  • Feeds — the other side of ad-network plumbing (product catalog feeds rather than event tracking).
  • Channels — integrations the merchant connects for outbound marketing.
CurrencyRupiah is paid by transfer or QRIS; US dollars settle through PayPal.