Currency

Pixels

The pixels namespace stores a merchant's analytics tracking config — Meta pixel + Conversions API access token, TikTok pixel, Google Analytics ID, Google Ads conversion ID and purchase label — and exposes a redacted public read so storefronts can fire client-side events without ever seeing the CAPI secret. This page covers ripllo.pixels on the Node SDK. For the HTTP surface, see API → Pixels.

Namespace

ripllo.pixels.get()
ripllo.pixels.update(input)
ripllo.pixels.public(accountId)

The first two are merchant-authenticated CRUD; the third is anonymous and used by the storefront <head> injector to learn which pixels to mount. The split is the entire reason this resource exists — one shape with secrets for the dashboard, a slim shape without secrets for the public.

Methods

pixels.get

Signature. ripllo.pixels.get(): Promise<MerchantPixels>

Returns the full pixel config for the calling workspace, including the Meta CAPI access token. Use this in the dashboard's pixel-settings page. A merchant who has never saved a config gets an all-null stub with enabled: true rather than null.

const pixels = await ripllo.pixels.get();
console.log(pixels.metaPixelId, pixels.metaCapiAccessToken ? '(set)' : '(unset)');

pixels.update

Signature. ripllo.pixels.update(input): Promise<MerchantPixels>

PATCH semantics — pass only the fields you want to set. Pass null to clear a field; passing undefined (the default for absent keys in a partial object) leaves it untouched. Upserts, so the first call creates the row.

await ripllo.pixels.update({
  metaPixelId: '1234567890',
  metaCapiAccessToken: 'EAAGm0PX...',
  metaTestEventCode: 'TEST12345',
  googleAnalyticsId: 'G-ABCD1234',
  googleAdsConversionId: 'AW-1234567890',
  googleAdsPurchaseLabel: 'abcDEF123ghi',
  tiktokPixelId: 'C8R12345...',
  enabled: true,
});

// Clear a pixel:
await ripllo.pixels.update({ tiktokPixelId: null });

Unknown keys are dropped silently. The PATCH body is zod-parsed and only recognised keys reach the database, so update({ metaCapiToken: '…', ga4MeasurementId: '…' }) returns 200 OK having stored nothing. Those names do not exist — use metaCapiAccessToken and googleAnalyticsId. There is no ga4ApiSecret and no tiktokAccessToken field at all: Ripllo stores no GA4 Measurement Protocol secret and no TikTok Events API token.

pixels.public

Signature. ripllo.pixels.public(accountId): Promise<PublicPixels | null>

Anonymous read — returns the merchant's pixel IDs without any server-side secrets. Storlaunch's storefront calls this at SSR time to know which <script> tags to inject; the Meta CAPI server-side flow never ships through this surface.

const ids = await ripllo.pixels.public('acc_<merchant>');
if (ids?.metaPixelId) injectMetaPixel(ids.metaPixelId);
if (ids?.googleAnalyticsId) injectGa4(ids.googleAnalyticsId);

Returns null in three cases: the merchant has never set up pixels, the config exists but enabled is false, or every ID on it is empty. So a null here is not proof the workspace is missing — it can equally mean "tracking is switched off". Just check the response.

Types

interface MerchantPixels {
  id?: string;
  accountId?: string;
  metaPixelId: string | null;
  metaCapiAccessToken?: string | null;   // server-only; never on PublicPixels
  metaTestEventCode?: string | null;     // for Meta's Test Events tab
  googleAnalyticsId: string | null;
  googleAdsConversionId: string | null;
  googleAdsPurchaseLabel: string | null;
  tiktokPixelId: string | null;
  enabled: boolean;
}

interface PublicPixels {
  metaPixelId: string | null;
  googleAnalyticsId: string | null;
  googleAdsConversionId: string | null;
  googleAdsPurchaseLabel: string | null;
  tiktokPixelId: string | null;
  enabled: boolean;
}

PublicPixels carries no accountId — you already know which workspace you asked for. The Node SDK's update accepts Partial<MerchantPixels>, so every field above (including metaCapiAccessToken) is settable through that method; only the public read strips the secret.

Common patterns

SSR storefront injection

// In Storlaunch's `/s/:merchant` route handler:
const pixels = await ripllo.pixels.public(merchant.accountId);
return renderHead({
  metaPixel: pixels?.metaPixelId,
  ga4: pixels?.googleAnalyticsId,
  tiktok: pixels?.tiktokPixelId,
});

Cache the public response — pixel IDs change rarely and the call is on the hot path for every storefront pageview.

Server-side Meta CAPI event

The CAPI flow lives on your backend, not in Ripllo — Ripllo only stores the credentials. Pull them at request time, build the event, and ship it to Meta:

async function sendCapiPurchase(orderId: string) {
  const pixels = await ripllo.pixels.get();        // merchant key — has secrets
  if (!pixels.metaPixelId || !pixels.metaCapiAccessToken) return;

  await fetch(`https://graph.facebook.com/v18.0/${pixels.metaPixelId}/events`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      data: [{ event_name: 'Purchase', event_time: Date.now() / 1000, /* ... */ }],
      access_token: pixels.metaCapiAccessToken,
      test_event_code: pixels.metaTestEventCode ?? undefined,
    }),
  });
}

Don't proxy this through Ripllo — latency and rate-limit budgets belong to your service, not ours.

Migrate from inline pixel IDs to Ripllo

If you used to store pixel IDs directly in your product's DB, the migration is one update call per merchant:

for (const m of merchants) {
  await ripllo.pixels.update.bind(ripllo.forMerchant(m.accountId).pixels)({
    metaPixelId: m.legacy.fbPixel,
    googleAnalyticsId: m.legacy.ga4,
    googleAdsConversionId: m.legacy.googleAds,
  });
}

(forMerchant returns a client clone scoped to a specific workspace — only valid with a platform-admin key.)

Errors

Code Status Cause
VALIDATION 400 An unrecognised type, or an ID over its length cap (64 chars, except metaCapiAccessToken at 500). Format is not checked — a non-numeric Meta pixel ID is stored as given.
NO_ACCOUNT 403 The principal carries no accountId.
INSUFFICIENT_SCOPE 403 API key lacks read (for GET /pixels) or write (for the PATCH). ripllo:platform:admin is a superset of both.

public(accountId) does not 404 for an unknown workspace — it answers 200 with data: null, the same as a disabled or empty config.

See Errors for the full handling guide.

Next

CurrencyRupiah is paid by transfer or QRIS; US dollars settle through PayPal.