POST an offer_completed event to a URL you own. Use it when your server needs to react to a completion: grant a reward, credit a balance, kick off an email. You host one HTTPS endpoint; nothing to poll.
This is the final stage of the shared flow, and it fires on verification (the advertiser’s conversion postback), not on the user tapping or claiming; a claim that never converts produces no webhook. The payload contract (headers, body fields, signature spec, delivery guarantees, URL requirements) is the offer_completed webhook reference; this guide is the wiring.
Set it up
Configuration lives in the dashboard under Settings for the app you want events for; there is no publishable-key API for webhook config.1
Enter your webhook URL
Paste the HTTPS URL of your endpoint (for example
https://your-server.com/webhooks/encore) and save. One webhook per app: to point somewhere else, edit the existing URL. It must be HTTPS, publicly resolvable, and redirect-free (URL requirements).2
Copy your signing secret
Creating the webhook generates a
whsec_... signing secret. Store it as a server-side secret (an environment variable, never in client code). Rotating it invalidates the old one immediately: update your server first, or you’ll reject deliveries in the gap.3
Send a test delivery
Send Test POSTs a sample event to your live URL with a real signature from your current secret, exercising your verification path end to end. The sample’s
transactionId and userId are not real records, so make sure your handler tolerates an unknown user gracefully instead of throwing.4
Enable or pause
The status toggle turns delivery on and off. Retries cover a failing endpoint, not a paused one: events that occur while paused are lost, not queued, and pausing also stops retries already in flight.
Verify the signature
Never trust an unverified delivery; the signature proves the request came from Encore. Recompute HMAC-SHA256 over<X-Webhook-Timestamp>.<raw_body> with your whsec_... secret and compare hex digests (signature spec).
offer_completed webhook reference.
Design for the guarantees
Encore retries a failed delivery up to five times over roughly nine hours, with a 5-second timeout per attempt and no ordering guarantee (full table). Three consequences worth designing for:- Commit the credit before you return
2xx. Your2xxis what tells Encore the reward landed, and it is what stops the retries. Acknowledge on receipt and credit afterwards, and any failure in that later step leaves the user uncredited with no signal to us and no retry. If the credit is genuinely too slow for the 5-second budget, write it to your own durable queue inside the request and treat that write as the commit. - Handle duplicates idempotently. Key on
transactionId: record processed ids and make a second delivery for the same id a no-op. A user can have several transactions, so the transaction is the unit, not the user. - Reconcile anything financial. Retries narrow the gap but do not close it: a delivery can still exhaust its attempts if your endpoint is down for the whole window. Do not treat this webhook as your ledger. Reconcile periodically against your Encore dashboard analytics.
Testing locally
The SSRF guard refuses private addresses, sohttp://localhost:3000 can never be a webhook URL, including for test deliveries. Expose your local server through a tunnel with a public HTTPS hostname (ngrok, Cloudflare Tunnel, Tailscale Funnel), point the webhook at the tunnel URL, and use Send Test. On the demo campaign, a test-conversion call from your own integration simulates the advertiser postback, so you can exercise the full flow without a live advertiser.
Troubleshooting
Signature never matches
Signature never matches
Almost always a body-bytes problem. Verify against the raw request body, not
JSON.stringify(req.body). Also confirm you’re joining timestamp and body with a literal dot, hex-encoding (not base64), using the header timestamp rather than your own clock, and using the current secret if you recently rotated it.Send Test reports a timeout or connection error
Send Test reports a timeout or connection error
A timeout means your endpoint took longer than 5 seconds: return
2xx as soon as the signature verifies. A connection error means the URL isn’t HTTPS, the host isn’t publicly resolvable (use a tunnel), or the path redirects (redirects are rejected, not followed). Send Test is a single attempt and is never retried, unlike a real completion.No events arriving in production
No events arriving in production
Confirm the status toggle is enabled, and remember the event fires on verified completions, driven by the advertiser, not by claims. Run Send Test first to isolate reachability from event volume.
A completion appears in the dashboard but no webhook arrived
A completion appears in the dashboard but no webhook arrived
Check whether it is still in flight before treating it as lost: a failed attempt is retried at +5 min, +10 min, +30 min, +2 hours and +6 hours, so a delivery can be up to roughly nine hours behind the dashboard. If nothing has arrived after that, every attempt failed (endpoint down, non-2xx, timeout, TLS error) and the delivery was abandoned. Abandoned deliveries are not self-serve replayable, so reconcile from dashboard analytics and contact support if you need the event resent.
Related
- offer_completed webhook reference: the payload contract, signature spec, and delivery guarantees.
- Rewarded Actions guide: the flagship flow whose completions this webhook reports.