Webhooks
When a conversion is recorded, Routy calls a URL you choose and writes that conversion's own values into the address. One webhook per ad network postback, or one that catches everything.
What this feature does
A webhook is a URL Routy calls when a conversion happens. You pick the address and the method, GET or POST, and you write the details you need into the URL as placeholders: https://tracker.example.com/pb?cid=${externalClickId}&event=${eventName}&payout=${eventValue}. Each time the webhook fires, Routy resolves those names against that conversion, so the request arrives as ?cid=abc123&event=FTD&payout=25.5. That is the request nearly every ad network and tracker asks for when it says "give us a postback URL".
Webhooks come in two scopes. An affiliate-level webhook fires for every conversion under your account, whichever traffic source produced it, which suits a CRM or any single catch-all endpoint. A traffic-source webhook fires only for conversions attributed to the source you bind it to, which is how you send one ad platform its own conversions and nothing else. You get five of each, counted separately, and both fire when both match.
A POST sends Routy's full conversion payload as JSON on top of the resolved URL: click id, click date, event name and value, currency, conversion type, account, brand, program, traffic source, the click's attribution block with gclid, fbclid, parameterA, parameterB and externalClickId, and the page views that led up to it. A GET sends no body, so the URL is all you have, which is why the placeholders matter.
What you'll get out of it
- Per-conversion values anywhere in the URL:
${clickId},${externalClickId},${eventName},${eventValue},${currencyCode},${eventTime},${eventTimeStamp},${conversionId},${conversionType},${parameterA},${parameterB},${gclid},${fbclid},${msclkid},${obclid}, and names like${trafficSource},${account},${brand}and${program}, each with an id form such as${brandId}. - Your traffic source's own click id and your sub-IDs among them.
${externalClickId},${parameterA}and${parameterB}are the values most postbacks are built around. A webhook written before they were reachable starts sending them on its next conversion, with nothing to change at your end. - Names that don't have to be typed carefully. Case doesn't matter,
{{name}}works the same as${name}, and{{{name}}}gives you the raw value when you don't want it encoded. - Encoding done for you. A campaign named "summer & winter" used to break the address and now arrives intact.
- A conversion-type filter, so a webhook fires only on the events the receiver cares about. Leave it empty and every type fires it.
- Event renaming per conversion type, for a platform that wants
ftdrather than "First Time Deposit". - A URL checked when you save it. Every placeholder has to name a field Routy can fill and the whole thing has to resolve to an absolute http or https address, otherwise the save is refused and the message names the placeholder at fault.
- A delivery record per event with its status, attempt count and last attempt, plus a log per attempt holding the URL, the method, the request and the response your server sent back. Filter events by webhook, click id, conversion id, status or date, resend one, or cancel a range in bulk.
- Simulate, which fires a sample event at your URL so you can check the contract before a real conversion turns up.
- Failures raised as issues on the webhook, by email as well, instead of sitting in a log you have to go and read.
How it actually works
Setting one up
Open the outgoing webhooks settings page and add a webhook. You give it a name, the URL with whatever placeholders you want in it, a traffic source or none, GET or POST, the conversion types that should trigger it, and optionally a custom event name per type. Over the API that's POST /v1/conversions/outgoing/webhooks/settings, where leaving trafficSourceId out or setting it to null is what makes the webhook affiliate-level. The caller needs account-admin rights; deleting events or cancelling them in bulk also needs the delete-traffic-source permission.
Then use Simulate. It answers that the request has been sent rather than showing you the response, so the outcome is in that webhook's delivery log a moment later.
Two things you can't configure, and together they decide what a webhook can talk to directly. There are no custom headers, and there is no body template: a POST always sends Routy's JSON payload with Content-Type: application/json. An endpoint that wants an API key has to accept it in the URL, which the placeholders handle. An ad platform API that insists on its own JSON structure and an authorization header needs an endpoint of yours in between, taking Routy's payload and making that call itself.
Placeholders, and the ones that stay empty
Write each name on its own, as ${clientUserAgent} rather than ${mainAttributions.clientUserAgent}. A value this conversion doesn't have renders as an empty string, so a click that never carried a gclid produces a blank there rather than an error.
A handful of names resolve under Simulate and are never filled by a real conversion: medium, campaign, term, content, creative, location, locationPath and documentTitle. A test that looks right and a live postback that arrives half empty is usually one of those.
A URL saved before the save-time check existed can still be broken at send time. That raises a "Webhook URL Is Invalid" issue, by email too, and the issue says which of the two cases you're in: postbacks stopped, because a placeholder isn't a plain name or the URL doesn't resolve to a web address, or postbacks are going out with one value blank, because a placeholder names nothing Routy holds. Correct the URL and the conversions that couldn't be sent go out by themselves over the following days, since a retry resolves the webhook's current URL rather than the one that failed.
When your server doesn't take it
A delivery that fails is retried roughly once an hour, up to ten attempts, for up to ten days. That covers a server that times out, refuses the connection, fails DNS or TLS, or answers with a status outside the 2xx range.
An "Outgoing Webhook Failed" issue goes up against the webhook, with the error type named, and it closes on the next postback that webhook gets accepted. An alert still showing is therefore still failing, which is the point of it. A webhook's own status is Enabled, Disabled or Suspended, and a suspended webhook sends nothing until you set it back to Enabled.
The delivery log stores the URL template rather than the resolved address, so the log tells you what was configured and your receiver is the only place that knows exactly what arrived.
Limits and cost
Five webhooks per traffic source and five affiliate-level webhooks per account, as separate limits, so five on one source doesn't touch the affiliate-level count.
Because both scopes fire independently, an affiliate-level webhook plus a traffic-source webhook on the same source means your endpoint gets the same conversion twice. That's by design, not a bug to report, and the fix is to keep one of the two.
Outbound conversion postbacks are metered against your plan's webhook allowance. Sends keep going until usage passes 110% of it; past that an event is held rather than sent, and retried every couple of hours instead.
Some conversion types are marked as not eligible for postbacks. Routy's calculated commission types carry that flag, and so does Click, which is kept out of a webhook's conversion-type list unless click webhooks are turned on for your account.
Why this is worth doing
If you buy traffic on Google Ads, the webhook isn't the first thing to reach for. Routy has a traffic-source integration that uploads offline conversions on the gclid, with the conversion action mapping you set, a retry every 30 minutes and a history of what was sent. Meta, Microsoft Ads and Outbrain are listed as future work on that pipeline, so for those the webhook is what exists, and what it gives you is a postback URL carrying fbclid, msclkid or obclid plus the event name and value.
For a tracker or a network, a postback URL with the click id and the payout in it is the whole integration, and that is exactly what a webhook produces, with no scheduled export or reconciliation step in between.
For your own systems the affiliate-level webhook is the one to use: every conversion in your account arrives at one endpoint with the click, the attribution and the page views attached, so a CRM write, a fraud check or a fulfilment job has what it needs without anybody pulling a CSV.
What it costs you is control over the request. You choose the address and the method and nothing else, so anything that needs a particular body or header is a hop away rather than direct. And a delivery that keeps failing is visible to you as an issue and an email, not as a queue you can inspect request by request.
Frequently asked questions
Can Routy post directly to Meta's Conversions API or TikTok's Events API?
Not in the form those APIs want. A webhook gives you the URL and the method, not the body or the headers, so an ad platform that requires its own JSON and an authorization header needs an endpoint of yours in the middle. For Google Ads there's a dedicated traffic-source integration that uploads conversions on the gclid, which is the better route.
What happens if my endpoint is down?
The delivery is retried about once an hour, up to ten attempts over ten days, and an "Outgoing Webhook Failed" issue goes up naming the error. It closes on the next postback your server accepts.
How many webhooks can I have?
Five per traffic source and five affiliate-level per account. The two limits are separate.
Can I filter which conversions trigger a webhook?
By conversion type, by the traffic source the webhook is bound to, and by attribution source. Not by conversion amount.
Will I see delivery logs?
Yes. Every attempt is logged with the URL, the method, the request and your server's response, alongside the event's status and attempt count. The URL logged is the template, so the resolved values are only visible at your end.
Why did my endpoint receive the same conversion twice?
You have an affiliate-level webhook and a traffic-source webhook that both match it. Both fire. Remove one if you want a single request per conversion.
A value in my postback URL is arriving empty.
Either the original click never carried it, which is common for gclid, fbclid and the sub-ID parameters, or it's one of the names that only Simulate fills. Check the click's own URL first.
Ready to try Webhooks?
Open the outgoing webhooks settings page, add a webhook with your URL and the placeholders the receiver expects, and run Simulate before you wait on a real conversion.