Decline widget
Use this when your app has already decided a user is not a fit (risk, KYC, rules, etc.) and you want to show alternative sponsored offers instead of an empty “no” screen. The same overlay UI as geo-blocking applies; only how it is opened differs: your server must call trigger — the integration secret never goes in the browser.
Install in three steps
1. Dashboard — Open the site → Site details → turn on Decline widget. Confirm your domain is allowed. Copy Publisher key, Website key, and the integration secret (keep the secret on the server only; never in NEXT_PUBLIC_* or client bundles).
2. Your page — Add the widget-decline.js snippet (below) to the page where the declined user lands. Must be the same domain you registered.
3. Your server — The script creates a session id and waits. Send that id to your backend, then call POST /v1/widget-decline/trigger with Authorization: Bearer and { "sessionId": "…" } when you want the overlay. Use the same sessionId the embed created — do not mint a second id with your own fetch.
---
Prerequisites (detail)
1. Site details: Decline widget enabled; domain allowlisted for embeds.
2. Keys: public keys in the HTML snippet; integration secret only in server env / secrets manager.
3. Where to load: any page on that domain after your own “declined” path (dedicated URL, modal step, etc.).
What the embed does
| Step | What happens |
|---|---|
| 1 | On load, the script POSTs to https://api.affilfinder.com/v1/widget-decline/session?pub=…&website=… and receives sessionId and expiresAt. |
| 2 | It exposes sessionId as window.__AFFILFINDER_DECLINE_SESSION_ID and dispatches a browser event affilfinder:decline-session with detail: { sessionId }. |
| 3 | It polls GET …/v1/widget-decline/session/:sessionId?pub=…&website=… until status is ready or the session expires (~15 minutes). |
| 4 | Your server calls POST /v1/widget-decline/trigger with the integration secret and JSON { "sessionId" } when you want to show offers. |
| 5 | After trigger, the embed loads offers and renders the same style of overlay as the geo-blocking widget. |
Script loading: the embed looks up the active tag (data-pub / data-website). Prefer a synchronous tag (no async / defer) so the script can resolve itself reliably; if you use a loader, ensure the tag still carries those attributes.
Optional: data-api (override API origin), data-dev-country / data-dev-region (local testing only), data-comments="false" (quieter logs).
---
Load widget-decline.js
<script
src="https://affilfinder.com/widget-decline.js"
data-pub="YOUR_PUBLIC_KEY"
data-website="YOUR_WEBSITE_KEY"
></script>Set NEXT_PUBLIC_AFFIL* (or PHP env vars) to your public keys only — never the integration secret in the HTML layer.
---
Client: read sessionId and call your backend
Your server needs sessionId when it calls trigger, but the secret stays on the server. Typical pattern: the browser sends sessionId to your API (cookie session, signed JWT, or one-time token), then your API calls AffilFinder.
// React client — app/declined/decline-bridge.tsx ("use client")
"use client";
import { useEffect } from "react";
export function DeclineBridge() {
useEffect(() => {
function onSession(ev: Event) {
const ce = ev as CustomEvent<{ sessionId: string }>;
const sessionId = ce.detail?.sessionId;
if (!sessionId) return;
void fetch("/api/affilfinder/decline-arm", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sessionId }),
credentials: "same-origin",
});
}
window.addEventListener("affilfinder:decline-session", onSession);
const existing = window.__AFFILFINDER_DECLINE_SESSION_ID;
if (typeof existing === "string" && existing) {
void fetch("/api/affilfinder/decline-arm", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sessionId: existing }),
credentials: "same-origin",
});
}
return () => window.removeEventListener("affilfinder:decline-session", onSession);
}, []);
return null;
}The example routes sessionId to /api/affilfinder/decline-arm — name it whatever fits your app. That route should authenticate the user and store sessionId server-side (memory, Redis, or your DB) keyed to the session until you call trigger.
When your business logic decides to show offers (e.g. right after marking the user “declined”), call trigger from the same request handler or a background job.
---
Server: call POST /v1/widget-decline/trigger
Use Authorization: Bearer or header x-integration-secret (same value). Body: { "sessionId": "dcl_…" }.
// Next.js App Router — app/api/affilfinder/trigger/route.ts
import { NextResponse } from "next/server";
const API = process.env.AFFILFINDER_API_URL ?? "https://api.affilfinder.com";
export async function POST(req: Request) {
const secret = process.env.AFFILFINDER_INTEGRATION_SECRET;
if (!secret) {
return NextResponse.json({ error: "Missing AFFILFINDER_INTEGRATION_SECRET" }, { status: 500 });
}
const { sessionId } = (await req.json()) as { sessionId?: string };
if (!sessionId) {
return NextResponse.json({ error: "sessionId required" }, { status: 400 });
}
const res = await fetch(`${API}/v1/widget-decline/trigger`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${secret}`,
},
body: JSON.stringify({ sessionId }),
});
if (!res.ok) {
const text = await res.text();
return NextResponse.json({ error: text || res.statusText }, { status: res.status });
}
return NextResponse.json(await res.json());
}Wire triggerDeclineOverlay(sessionId) (or your PHP function) from the code path where you already know the user should see offers — for example immediately after your own API returns 403 / “not eligible”, or after you persist a “declined” row.
---
End-to-end sequence (checklist)
1. Render a page that includes widget-decline.js with valid data-pub / data-website.
2. Optionally notify your backend with sessionId from the event or window.__AFFILFINDER_DECLINE_SESSION_ID.
3. When your product logic is ready, POST trigger with the same sessionId and your integration secret.
4. The embed receives ready on the next poll and shows the overlay.
Security
- Store AFFILFINDER_INTEGRATION_SECRET in environment variables or a secrets manager — never in client code or
NEXT_PUBLIC_*. - Authenticate any route that accepts
sessionIdfrom the browser so third parties cannot arm sessions for other users.
Related
- REST API reference — full
/v1/widget-decline/*details. - Widget integration reference
- Geo-blocking widget
- How AffilFinder works
Need more help?
Can't find what you're looking for? Reach out to our team and we'll get you sorted.