Agnic
Partner Program

Partner-Branded Add Card

Let your users vault a payment card with Agnic without leaving your brand — one deep link, zero PCI scope for you.

Agent purchases on the card rail need a vaulted card. Partner-Branded Add Card is the hosted page that gets one on file: your user clicks "Add a card" in your app, a page wearing your wordmark and accent color opens, and the card goes straight into VGS — Agnic's PCI-certified vault. Neither your app nor Agnic's frontend ever sees the card number.

It works exactly like Partner-Branded Checkout:

  • Wears your wordmark and accent color (same dashboard settings)
  • Opens as a desktop popup or mobile full-page redirect
  • Signals your app the moment the card is vaulted — postMessage on desktop, ?card=success on mobile

Card entry is rendered by VGS-hosted iframes. The card number, expiry, and CVC never exist in your page, your domain, or even Agnic's JavaScript — only opaque aliases flow to the API. Your app stays fully out of PCI scope (SAQ-A on Agnic's side).


The flow

┌────────────────┐                                          ┌────────────────┐
│                │  1. user clicks "Add a card"             │                │
│  Your app      │─────────────────────────────────────────▶│  popup /       │
│                │                                          │  redirect      │
└────────┬───────┘                                          └────────┬───────┘
         │                                                           │
         │                                   2. Agnic Add Card loads
         │                                   (your brand, VGS iframes)
         │                                                           │
         │                                   3. user enters card → VGS vault
         │                                                           │
         │     4. 'agnic:card_added' postMessage / redirect          │
         │◀──────────────────────────────────────────────────────────┘

         │     5. your app updates its UI ("card on file ✓")

  user's agents can now pay on the card rail

Responsibilities:

PartyOwns
YouOpen the deep link; listen for completion
AgnicRenders the branded page; VGS card collection; vaulting

You never touch card data. There is nothing card-shaped to store on your side.


Step 1 — Brand the page

Uses the same two dashboard fields as Checkout — if you've already set them up, you're done:

  1. Go to app.agnic.aiOAuth Clients
  2. Click your client → Checkout settings
  3. Set Display name and Accent colorSave

URL format

https://app.agnic.ai/partner/cards/new
  ?client_id={YOUR_CLIENT_ID}
  &return_url={URL_ENCODED_RETURN}
ParamRequiredNotes
client_idYour app_… client id. Must be approved and not revoked.
return_urlAbsolute URL, URL-encoded. Must match one of the client's registered redirect_uris.

Reference implementation

const handleAddCard = () => {
  const returnUrl = `${window.location.origin}/your-page`;
  const clientId = process.env.NEXT_PUBLIC_AGNIC_OAUTH_CLIENT_ID!;
  const url = `https://app.agnic.ai/partner/cards/new?client_id=${encodeURIComponent(clientId)}&return_url=${encodeURIComponent(returnUrl)}`;

  // Popup on desktop, full redirect on mobile (narrow viewport).
  if (window.innerWidth < 640) {
    window.location.href = url;
    return;
  }

  const width = 480;  // keep ≥ 480 — the expiry/CVC grid needs the room
  const height = 760;
  const left = Math.max(0, Math.round(window.screenX + (window.outerWidth - width) / 2));
  const top = Math.max(0, Math.round(window.screenY + (window.outerHeight - height) / 2));
  window.open(url, "agnic-add-card", `width=${width},height=${height},left=${left},top=${top},popup=yes`);
};

Open the popup at least 480 px wide. The card form renders expiry and CVC side by side; narrower popups cramp the VGS iframes.


Step 3 — Handle the return

Listen for both callbacks — desktop uses postMessage, mobile uses the redirect params.

Desktop: postMessage

useEffect(() => {
  const onMessage = (ev: MessageEvent) => {
    // Origin check: only trust messages from Agnic.
    if (ev.origin !== "https://app.agnic.ai") return;
    if (!ev.data || typeof ev.data !== "object") return;

    if (ev.data.type === "agnic:card_added") {
      // ev.data.last4: string, ev.data.brand: string | null — display only
      toast.success(`Card •••• ${ev.data.last4} added.`);
      refreshCardStatus();
    }
    if (ev.data.type === "agnic:card_cancelled") {
      // user closed the popup without adding a card — no-op
    }
  };
  window.addEventListener("message", onMessage);
  return () => window.removeEventListener("message", onMessage);
}, []);
typePayloadWhen
agnic:card_addedlast4: string, brand: string|nullCard vaulted successfully
agnic:card_cancelledUser closed the popup before finishing

The payload is display data only — no card identifiers or vault aliases are ever exposed.

Mobile: ?card=success on the return URL

useEffect(() => {
  const params = new URLSearchParams(window.location.search);
  if (params.get("card") === "success") {
    toast.success("Card added.");
    refreshCardStatus();
    params.delete("card");
    const qs = params.toString();
    window.history.replaceState({}, "", window.location.pathname + (qs ? `?${qs}` : ""));
  }
}, []);
ParamValuesNotes
cardsuccess | cancelledAppended to your return_url on mobile

Security model — what this means for you

ConcernHow Add Card handles it
Arbitrary redirect (open-redirect abuse)return_url must match a redirect_uri on your OAuth client. Rejected otherwise.
PCI scope for your appCard fields are VGS-hosted iframes. The PAN/CVC never exists in your page or Agnic's JS.
postMessage spoofing from another tabYou validate ev.origin === "https://app.agnic.ai" in your listener.
Who can add a cardThe user signs in to their own Agnic account inside the flow; cards are strictly user-scoped.
Sensitive data in callbacksOnly last4 + brand are emitted — never aliases, card ids, or tokens.

Testing

Manual smoke-test checklist

  • Desktop Chrome: popup opens 480×760, VGS card fields render
  • Add a test card → success screen → agnic:card_added fires with last4 → popup closes ~2 s later
  • Click ✕ in header → agnic:card_cancelled fires, popup closes
  • Mobile (viewport < 640 px): full-page redirect; success returns return_url?card=success
  • return_url not registered → error page renders, no card form shown
  • Signed-out user → inline sign-in inside the flow, then the card form

Troubleshooting

SymptomLikely causeFix
"This link is invalid."client_id doesn't exist or is mistypedConfirm the env var; check the client at app.agnic.ai
"This app isn't approved yet."OAuth client is still in reviewWait for approval (typically 1 business day)
"The return URL isn't registered for this app."return_url doesn't prefix-match any redirect_uriAdd the origin to redirect_uris in the dashboard
Card fields don't renderPopup too narrow, or VGS script blocked by an extensionOpen ≥ 480 px wide; test without ad blockers
postMessage never firesListener not installed, or origin check too strictOrigin is exactly https://app.agnic.ai; log all messages
Popup blocked on desktopwindow.open called outside a user gestureOnly call it from a click handler

FAQ

Can I embed this in an iframe instead of a popup? No. The page ships X-Frame-Options: DENY deliberately — a card-entry surface must never render inside another site's frame (clickjacking), and the user's Agnic session isn't available inside third-party iframes anyway. The popup gives the same "never left your app" feel with none of those problems.

Can I read the card details my user added? No. You get last4 and brand for display, nothing else. Cards belong to the user's Agnic account, not to your app.

Does adding a card earn commission? The card itself doesn't, but agent purchases it enables are attributed to your Partner ID when they flow through your integration.