Skip to content
megapromotingLet's talk

Expertise · Marketing automation

A working environment in which the agent that handles your Google Ads and Meta campaigns reads the reports, proposes the change with the reason written down, and executes only within the limits you gave it.

The service is an offer, not a delivery, and we say it first: today we do not have a system that changes budgets or bids in Google Ads or Meta. We have built and run the parts that make one — sending the real conversion signal to Google, a motor that buys paid promotion with dry-run on by default, a register that writes every action that costs, and a switch that stops an orchestrator without a bypass route.

An offer, with conditions“offer”, not “delivered”, and the reason can be checked in a few seconds. I searched across more than 150 of our own repositories for the fields through which ad APIs move money — `budget_micros`, `campaignBudget`, `target_cpa`, `target_roas`, `bid_strategy` — and they do not appear anywhere, neither at Google nor at Meta. We do not manage ad budgets today. What does appear, and can be opened file by file, are the pieces that separate automation from an accident: sending offline conversions to Google via the Data Manager API, 264 lines in one of our production projects; a paid promotion engine that buys boosters, labels and packages with dry-run enabled by default; a Bayesian A/B tester that refuses to declare a winner below 100 impressions per variant, and the campaign orchestrator in MEGA CRM with a switch without `--force`. The service is described from these pieces. Not from a panel that does not exist.

An agent that handles campaigns is not a button that says “optimise”. It is a programme that is allowed to read certain reports, allowed to write certain fields, and no other permission besides. The hard part is not proposing a change — models do that well and fast. The hard part is writing where it stops, how it proves what it did, and what happens when it makes a mistake. That is what the work is about, and that is where the discussion starts.

We say from the start what we do not have, so you do not find out from someone else. We do not run today a system that raises a budget in Google Ads or that changes a bid in Meta. What does run, and can be shown line by line, are the safety mechanisms we wrote for other automations that spend real money: dry-run on by default, a counter on each paid action, backoff on error so the mistake does not repeat in a loop, a proof threshold before a decision, and a switch that refuses to start the orchestrator.

The piece we already have built is not a budget change, but a signal — and that is the one we propose to start with. Google learns from what you tell it is a conversion. If you tell it “form submitted”, it will look for people who submit forms. We wrote the integration that tells it “request actually reached the client’s system”, with the click identifier and user identifiers passed through SHA-256, through the Data Manager API. That can start immediately, because it is existing code, not a promise.

The rest of the working environment — the layer that reads reports, the layer that proposes, the layer that executes — is built on the same pattern we used where we have already spent money through code. It starts strictly with read-only access, moves to proposals without execution, and only at the end opens execution, field by field, with a written limit on each.

What it covers

The work, by component

Read the reports, not the dashboard

From the start it is set what the agent reads and from where: spend by campaign and ad group, attributed conversions, cost per acquisition, lost impression share due to budget versus lost due to rank — two different figures that require different decisions. Reading is done through the API, with a separate service account, not through screenshots from the interface. The read-only layer is delivered and started first, on its own, with no write permission on the account.

Propose the change with the reason written alongside it

A proposal is not a number. It is the field that changes, the old value, the new value, the data window on which the decision was made, and why. Proposals are gathered into a report that a person reads, in the form “I would raise campaign X’s daily budget from A to B because in the last N days it was limited by budget in M% of auctions, at a cost per acquisition below the agreed threshold.” If the reason cannot be written, the proposal is not made.

Three thresholds: what it does by itself, what it asks a person, what it never does

Each field falls into one of three categories, written before anything starts. By itself: changes under an agreed percentage, within a daily cap, on campaigns marked as open. With approval: any overrun of the cap, any new campaign, any new audience, any bid change. Never: stopping a campaign it did not start, changing the payment method, touching billing accounts. The list is written in the contract, not in code, and the code reads it.

Dry-run on by default — the pattern we bring with us

In our paid promotion engine on 999.md, the function that decides whether money is spent is written the opposite way from how it is usually written: dry-run is active if the environment variable does not have the exact value `false`. Unset, empty, `true`, misspelled — all keep dry-run on. It is a deliberate choice: a configuration mistake cannot open the tap, it can only keep it closed. The same function exists in two independent places, the promotion engine and the republishing executor.

Each action that costs money is written in a register

In the promotion system, every execution goes into an `agent_costs` table with the ad, the user, the action type, the amount in lei, whether it succeeded, the provider’s raw response, and a `dry_run` marker. Writing happens in the same transaction as updating the schedule, so there can be no expense without a trace or a trace without an expense. When it is a dry run, the recorded amount is zero, but the row exists — you can see exactly what would have happened.

The real conversion signal — the piece that already exists

When someone comes from a paid click and submits a request, the signal is sent to Google only after the request enters the client’s system, not when the button is pressed. The integration uses `https://datamanager.googleapis.com/v1/events:ingest`, sends the click identifier, the moment, the value and the currency, plus user identifiers passed through SHA-256 — the normalised address in lower case, the phone number brought to international format. Without the click identifier, the submission stops by itself and is marked as skipped, because it would not be attributable.

Consent is set before the tags, not after

Consent Mode v2 is initialised with all marketing and analytics purposes set to `denied` before any Google script starts, with a 500 ms wait window for updating. It switches to `granted` only when the person interacts with the banner, without reloading the page. Functional and security storage remain enabled, because the page does not work without them. It is written once and runs on every visit.

The decision between two variants is made on evidence, not on impression

The A/B tester we built models each variant as Beta-Binomial with a uniform prior and estimates the probability that one is better through 10,000 Monte Carlo samples. It does not declare a winner below 100 impressions per variant and below a probability of 0.95. If after 168 hours it has not decided, it closes the experiment and says it expired, instead of naming a winner it cannot support.

A switch that cannot be bypassed

The campaign orchestrator in our internal system checks an environment variable before anything else. If it is set, it refuses to start — there is no `--force`, there is no argument that can override it. It was added after a real incident of duplicate submissions, not as decoration. The same pattern is put here too: an account-level switch that stops all writes, leaving reading and reporting to carry on.

What it looks like

The path, step by step.

01

The inventory of accounts and rights

It lists the advertising accounts, who has access today and with what role, which conversions are defined and which of them are actually duplicates of the same event. The three field lists are established — alone, with approval, never. We deliver: the written inventory, the list of conversions with what each one really measures, and the agreement on the three lists.

02

The reading layer, started on its own

Reading is connected through the API with an account that has no write right, and the daily report is started. Nothing changes in the accounts at this stage — you only see what the agent sees. This is the moment when conversions that were counting incorrectly, campaigns limited by budget without anyone knowing, and overlapping audiences come to light. We deliver: the daily report and the list of what we found by reading.

03

The real conversion signal

The click identifier is captured on the landing page, linked to the request in your system and sent to Google only when the request is confirmed downstream, not when the button is pressed. Retrying for failed submissions is enabled, within the 90-day window. We deliver: the working integration, a verification mode that sends without recording, and the log of what was sent and what was skipped and why.

04

The proposal layer, without execution

The agent starts writing proposals: field, old value, new value, reason, data window. Nothing is executed. It runs like this until the proposals become boring — that is, until the person reading them would have pressed “yes” anyway on almost all of them. We deliver: the proposal flow, with everything it proposed and what you approved or rejected, so that it is clear where it goes wrong.

05

The opening of execution, field by field

Execution opens for the first field in the "single" list, with dry-run enabled by default and with a daily cap. It is checked in the register that what was written is what was proposed. Then the second field. We deliver: the action register, the switch that stops all writes while leaving reading on, and the written procedure for what is done when the agent has made a mistake.

A decision path1citire prin API cuaccount without entitlement toscriere2proposal with field,valoare veche,new value and reason3filtrul celor treiliste (singur / cuapproval / never)4execution with dry-runenabled by default andplafon zilnic5row in the register ofcosts, written inthe same transactionThe stop switch cuts execution and leaves reading on.
A decision path

The data

What we touch, where it lives, and how long it stays

The questions anyone with a data protection officer would ask — asked here before they do.

What data it touches
Campaign reports, ad group and keyword read through the API. The ad click identifier, captured from the landing page address and kept on the client request. Conversion events with time, value and currency. At explicit request, the email address and phone number of the person who converted, but only in cryptographic summary form.
What goes to Google and in what form
The click identifier goes in clear — that is what it is, a click identifier. The email and phone number go only as SHA-256, calculated locally before sending: the email cleaned of spaces and converted to lower case, the phone brought to international format. The postal address is not sent at all, because Google’s schema requires it in full with postcode and region code, and the postcode is not consistent in the real data — the reason is written in code, not assumed.
Where they are kept and who sees them
Campaign data and events are kept in your project database, on the agreed infrastructure. The credentials for the ad accounts are kept in an environment file read by the system service, not in code and not in the source repository. The access token is kept in memory for at most 55 minutes, although the provider gives it a validity of 3,600 seconds — the margin is intentional, so that an expired one is never used.
How long an event can still be sent
The practical window is 90 days from the time of conversion — that is what Google accepts for offline conversions. Retry for failed sends works exactly within this window, in batches, with a maximum of five attempts per request. After five attempts, the request is left alone and remains marked as not sent, instead of being retried endlessly.
What we do not do with the data
We do not build public profiles from your client lists without a written legal basis and without it appearing in the notice seen by the person. We do not move data between the accounts of two different clients. We do not keep copies of campaign reports outside the agreed system. The processing register is completed before the first send, not after.

A case

A campaign that paid for forms, not for requests

The situation

A platform in Chisinau that receives online requests bought search traffic. The conversion defined in the ad account was "form submitted". The bidding algorithm did exactly what it was asked to do: it brought people who submit forms. How many of them became real requests, further down the chain, never reached the ad platform.

What we built

We moved the conversion moment later in the chain. The click identifier is captured on the landing page and kept on the request. The request goes further to the downstream system; if it returns its own identifier — that is, it really entered — only then does the event go to Google, through the Data Manager API, with time, value, currency and user identifiers passed through SHA-256. Without a click identifier, the send stops by itself and is marked as skipped. We added a retry job that looks for requests with a click identifier and without a send mark, in the last 90 days, in batches of 50, with no more than five attempts.

What came out

The signal the ad platform now receives describes requests that reach their destination, not button presses. All operations are inert if the integration is not configured or is turned off — the function checks all seven credentials before anything else, and returns "skipped" if one is missing. Errors are logged, but not propagated: if Google does not respond, the client request goes on untouched.

What the case does not say

This is a signal integration, not campaign management. It does not change any budget or bid. The effect on cost per acquisition has not been measured by us and we do not publish it — in code there is an author-written expectation, not a measurement, and that is the whole difference between the two.

Questions

What people ask us before they call

Who is responsible when the agent increases a budget and spending goes up?

You. And that is why the environment is built so that you can bear responsibility knowingly, not be caught by surprise. Concretely: the agent has no write permission that nobody gave it by name, in writing, field by field; every open field has a ceiling above which the proposal goes to human approval; every executed action is written into a register with the old value, the new value and the reason, in the same transaction as the execution; and there is a switch that stops all writes without stopping reads. We are responsible for what we built — that the limits you wrote are respected by code, that the register is complete, that the stop works. We are not responsible for the commercial result of a change you approved, just as the agency that presses the manual button is not responsible either. If someone promises you something else, ask them what the contract says under the liability limitation chapter.

Do you have a system today that manages Google Ads or Meta campaigns?

No. Nowhere in our code does any field appear through which a budget, a bid or a bidding strategy is changed at Google or Meta. We checked by searching for those exact field names across all repositories. What we do have is sending conversions to Google, an engine that buys paid promotion on another platform, and the safety mechanisms around them. The service is being built from there, and is marked as an offer precisely so that there is no confusion.

Then what exactly have you already built and spends real money?

Our paid promotion engine for a classified ads platform in Moldova. It places paid labels, starts boosters with a daily limit and cost per click, activates packages and republishes ads after an interval calculated from the category’s real speed. It refuses to start a booster whose daily limit is not greater than the cost per click — a small check, but exactly the kind of check that is usually missing. Every action goes through dry-run verification before any call that costs.

What can it change on its own and what requires approval?

It is decided together, in advance, and written down. Our starting point in the discussion: on its own — adjustments below an agreed percentage of the daily budget, within an absolute daily ceiling, on the campaigns you have marked as open; with approval — any exceeding of the ceiling, new campaigns, new audiences, any touching of bids; never — stopping campaigns started by someone else, payment methods, billing accounts. If you want everything on approval at the start, that is a valid choice and we prefer it too.

How does the agent know that a conversion is real and not an empty form?

Because the signal does not leave at the button press. It leaves only when the request is confirmed downstream, in the system that matters to you — a request identifier received back from the CRM or from the partner. Until then nothing has happened that deserves to be called a conversion. The difference is not cosmetic: Google’s bidding algorithm learns from what you send it, so if you send forms, it will bring you people who fill in forms.

What happens to the personal data of those who convert?

Email and phone are sent only as a SHA-256 cryptographic summary, calculated locally before sending, with normalisation — email without spaces and in lower case, phone brought to international format. The postal address is not sent at all. Nothing is sent before the person has accepted the marketing purpose in the banner, because Consent Mode starts entirely on `denied`. The legal basis and the information notice are written before the first send, not after someone’s first question.

What happens if the call to the advertising platform fails halfway through?

The failure is logged with the raw response, and the action is postponed — in our republishing executor, by the maximum of the configured cooling period and five minutes, explicitly so that an error does not turn into a call loop. Failed conversion submissions are retried through a separate job, in batches of 50, with at most five attempts per request, within the 90-day window. After the fifth attempt it stops and remains marked as unsent, so it can be seen, not disappear.

How do I stop it, completely and quickly?

The write switch. Reading and reporting continue, execution stops. In our existing systems this is implemented in two ways, both verifiable: an environment variable that must have exactly the value `false` for money to be spent — anything else keeps the tap closed — and a switch on the campaign orchestrator that refuses to start and has no `--force`. We do not depend on a button in an interface that may not load.

Can you guarantee a lower cost per acquisition?

No, and it is worth saying why, because in our code there is exactly such a figure. In the comment at the top of the conversion integration there is an expectation — how much the cost per acquisition could drop and how much the impression share could rise. It is an expectation written by the person who built it, not a measurement, and that is why you will not find it anywhere on this site as a result. What we can promise is that the measurement will be possible: with conversions that mean something and with a register of every change, the before-and-after comparison becomes possible. Without them, it is not.

What the statements above rest on (20 sources)

20 of them are code and files from our repositories. We don't publish their name or line: together, on a single page, they would describe too precisely how systems that aren't only ours are built. We go through them with you, in the repository, on request — verification stays possible, it just happens in a conversation.

What would you want to work better?

Tell us about your process. Together we decide what is worth building, what we can connect, and how we check the result.

Let's talk