01 — Architecture
How the integration works#
Three parties, one shared identifier. Vamofy gives every visitor a deterministic ID. You tell us what happens next, and we close the loop.
A partner publishes a Vamofy share link. When a visitor clicks it, we redirect to your destination with one extra parameter appended to the query string — vfy_click_id. That value is the only thing you need to remember.
When the visitor converts on your side, you send the same vfy_click_id back with a result type and an external event ID. We do the rest: match it to the partner, apply the agreed commission rule, credit the ledger, and release payment if the result is approved.
02 — Authentication
Connection keys#
Every server-side request carries one Bearer token: your connection key. Treat it like a database password.
- Method
- Bearer
- Header
- Authorization
- Prefix
- vfy_live_*
Create. From your brand workspace, open Tracking, then Create connection key. We show the full key once. Copy it to your secret manager — only a one-way hash stays on our side.
Use. Send the key in the Authorization header on every POST /api/vfy/convert request:
Authorization: Bearer vfy_live_a1b2c3...
Content-Type: application/jsonRotate. Create a new key, deploy it, then revoke the old one from the same screen. Revoked keys are rejected immediately — there is no grace period.
03 — Receiving traffic
Reading vfy_click_id#
We append a single query parameter to every outbound URL. Read it on first visit, persist it, and send it back with the conversion.
On the first request to your landing page, capture vfy_click_id from the URL. Store it in a way you trust — a first-party cookie, server-side session, or your own database. From that point on, attach it to anything your visitor does that you might want to convert: a checkout, a sign-up, a download.
We also pass sub-identifiers when a partner used a deeper share link. They are informational — you do not need them to attribute a conversion — but they are useful in analytics.
| Parameter | What it means |
|---|---|
| vfy_click_id | The unique ID for this visitor click. Always present. Use this for /api/vfy/convert. |
| subId1 | The partner's share slug (top-level affiliate). |
| subId2 | An optional channel tag picked by the partner (newsletter, story, paid). |
| subId3 | The visitor session ID when the click came from a Vamofy funnel. Use it if you need to deduplicate. |
// Read the visitor's vfy_click_id from the URL and remember it.
// Use any session store you trust — cookie, localStorage, or your own DB.
const url = new URL(window.location.href);
const fromUrl = url.searchParams.get("vfy_click_id");
if (fromUrl) {
localStorage.setItem("vfy_click_id", fromUrl);
document.cookie =
"vfy_click_id=" + encodeURIComponent(fromUrl) +
"; max-age=2592000; path=/; samesite=lax";
}
const vfyClickId =
fromUrl ||
localStorage.getItem("vfy_click_id") ||
document.cookie.match(/vfy_click_id=([^;]+)/)?.[1];04 — Server-side events
Report a conversion#
The main call. Send one POST when a sale, lead, or install happens on your side.
- Method
- POST
- Path
- /api/vfy/convert
- Auth
- Bearer
Server-side reporting is the source of truth. Send it from your backend the moment the conversion completes — after the charge clears, after the lead is verified, after the install is confirmed.
curl -X POST "https://sub-data-staging.vercel.app/api/vfy/convert" \
-H "Authorization: Bearer $VAMOFY_KEY" \
-H "Content-Type: application/json" \
-d '{
"event_id": "order_9b7e2",
"event_type": "purchase",
"click_id": "f5a29f4e-1d25-419c-93ab-f457a2508495",
"amount_cents": 9900,
"currency": "USD",
"metadata": { "sku": "VFY-PRO" }
}'Request fields
| Field | Status | Description |
|---|---|---|
| event_id | required | Your unique ID for this conversion. Reusing it is safe — we will return the original result instead of duplicating. |
| event_type | required | One of purchase, lead, install. |
| click_id | required | The vfy_click_id you captured on landing. |
| amount_cents | optional | Order value in the smallest currency unit (e.g. cents). Drives commission math. |
| currency | optional | ISO 4217 code. Defaults to the offer's currency, then USD. |
| occurred_at | optional | ISO-8601 timestamp. Defaults to now. |
| metadata | optional | Free-form object. Stored verbatim and surfaced in your reporting export. |
Response
On success we return the internal conversion ID and its current status. A duplicate event_id returns ok: true with duplicate: true and the original record.
{
"ok": true,
"id": "9a3c4a4e-8b1c-4d1e-9f3b-1d65ff7a8e10",
"status": "approved"
}05 — Browser pixel
Or use the browser pixel#
When you do not have a backend handy — or for low-stakes events like newsletter signups — drop a one-line script and call window.vamofyTrack.
The pixel reads vfy_click_id from the URL, persists it in localStorage and a first-party cookie, then exposes a single function on window.
Browser-reported conversions land as pending. Approve them in your brand dashboard or replay them server-side once you have verified the result.
<!-- Drop this into the page that proves the conversion happened. -->
<script async src="https://sub-data-staging.vercel.app/embed/pixel.js"></script>
<script>
window.addEventListener("load", function () {
window.vamofyTrack(
"lead", // event_type
"lead_" + Date.now(), // unique event_id
0, // amount_cents (0 for leads)
{ email: "reader@example.com" } // optional metadata
);
});
</script>06 — Event types
What counts as a conversion#
Three event types, picked to match how brands actually pay partners. Use the one that matches the commercial relationship.
01
purchase
Purchase
A paid transaction. Most direct-response brands live here. Commission can be a percentage of amount_cents or a flat amount per sale.
POS, checkout, subscription start
02
lead
Lead
A high-intent signal that is not yet revenue: form submission, demo booking, qualified call. Usually paid as a flat fee.
Email captured, demo booked
03
install
Install
App installs and free-trial activations. Common in mobile and SaaS funnels. Paid per install, optionally gated on activation.
App store install, trial start
The commission rule on the offer decides what each event pays. A single offer can pay on more than one event type — purchase on a sale, plus a smaller flat amount on an upstream lead.
07 — Idempotency
Safe to retry#
Every request is idempotent on event_id. Retry as aggressively as your queue wants to.
Network calls fail. Webhooks fire twice. Background workers crash mid-batch. Idempotency exists so none of that becomes your problem.
Choose an event_id that is unique within your system — an order ID, a checkout session ID, a UUID you mint at the moment of conversion. Send the same value on every retry. If we have already accepted it, we return the original result with duplicate: true. If we have not, we accept it and lock that ID for the lifetime of the conversion.
08 — Rate limits
Generous, predictable, transparent#
Limits exist to keep noisy neighbors from degrading anyone's send. Most brands never come close.
| Endpoint | Scope | Burst | Sustained |
|---|---|---|---|
| /api/vfy/convert | Per connection key | 60 / burst | 2 req/s |
| /api/vfy/pixel | Per IP address | 30 / burst | 0.5 req/s |
When you exceed a limit we return 429 with a Retry-After header. Honor it — back off for the suggested number of seconds, then retry. Limits scale up on request for verified high-volume partners.
09 — Errors
Predictable error envelope#
Every failure returns the same shape: ok: false, a stable machine code, and a human-readable message you can show in your own logs.
{
"ok": false,
"code": "missing_required_fields",
"error": "Send event_id, event_type, and click_id with this result."
}| Code | HTTP | What to do |
|---|---|---|
| missing_connection_code | 401 | Add a valid Bearer key in the Authorization header. |
| invalid_json | 400 | Send a syntactically valid JSON body. |
| missing_required_fields | 400 | Include event_id, event_type, and click_id. |
| unsupported_event_type | 400 | Use one of purchase, lead, or install. |
| click_not_found | 404 | vfy_click_id was not recognized. Confirm the visitor arrived through a Vamofy share link. |
| click_expired | 409 | The click is older than the offer's attribution window. Treat as not attributed. |
| click_not_approved | 422 | The click came from a partner who is not yet approved on this offer. |
| partner_not_approved | 404 | The partner's contract is paused, cancelled, or pending. |
| payout_rule_not_found | 422 | This offer has no commission rule for that event type yet. Add one in the offer settings. |
| rate_limited | 429 | Back off for the seconds in the Retry-After header, then retry. |
| result_not_saved | 500 | Retry with backoff. If it persists, contact support. |
10 — Conversion lifecycle
From pending to paid#
Every conversion walks the same path. The shape never surprises you.
Conversions land as pending. If the commission rule has auto_approve set, we move it to approved in the same request and write the partner's ledger entry immediately. Otherwise it waits for a human in your dashboard.
Approved conversions move to paid on the next payment cycle. Reversed and rejected are terminal states — they appear in reporting but do not pay.
11 — Testing
Test like it is production#
We do not run a separate sandbox host. Instead, every brand account ships with a self-service test loop.
Create a connection key labeled test. Create a partner profile, approve them on an offer, and use the generated share link to hit your real staging site. The click ID you receive is real and can be POST-ed back to /api/vfy/convert.
If you want to assert behavior without committing money, set amount_cents to 0 — the conversion still records, attributes, and writes to the ledger at 0, so you can validate the full round-trip without distorting reports.
When you are happy, revoke the test key and ship the production key from your secret manager.
| Method | Path | Summary |
|---|---|---|
| POST | /api/vfy/convert | Authenticated server-side conversion. Source of truth for purchases. |
| POST | /api/vfy/pixel | Unauthenticated browser-side conversion. CORS-open. Lands as pending. |
| GET | /embed/pixel.js | Drop-in 1 KB script that exposes window.vamofyTrack on your page. |
Everything else lives behind your brand account. Open Tracking, Offers, and Partners to wire up commission rules, invite partners, and review activity in real time.
Next step
Create your first connection key
Open Tracking in your brand workspace and create a key in under 30 seconds.
Talk to a human
Stuck or have a question?
Write us a note. We answer fast and we read every message.
Spotted a gap or have a request for the reference? Email developers@vamofy.com — we read everything.