webhook_url on
POST /v1/resolve and we deliver a signed POST to that URL when the job is
done, failed, or invalid.
Webhooks are optional and per-request. There is no global webhook endpoint — each resolve
decides where (if anywhere) its result is delivered via the
webhook_url field.Registering a webhook
Includewebhook_url in the resolve body. It must be a public HTTPS URL; loopback,
private, link-local, and .localhost / .internal hosts are rejected at validation time.
2xx (a 204 with no body is ideal). Do heavy work asynchronously —
a slow or failing endpoint triggers retries.
Events
resolve.done
The job resolved successfully.
target_url, provider, tier, and credits_charged are
populated; error is null.resolve.failed
The job ended in
failed or invalid. target_url is null and error carries the
reason (resolve_failed or unsupported_link).Payload
The body is JSON with these fields:"resolve.done" or "resolve.failed".ISO 8601 timestamp of when the event was generated.
The job UUID this event is about.
The terminal job status:
done, failed, or invalid.The resolved destination on success;
null on failure.The provider that owned the link (e.g.
ouo), or null."standard", "premium", or null.null on success; otherwise resolve_failed or unsupported_link.Credits spent on the job (
0 on failure).Your credit balance after the job, or
null if unavailable.- resolve.done
- resolve.failed
Verifying the signature
Every delivery carries anX-LinkSkipper-Signature header so you can confirm it really came
from Link Skipper and wasn’t tampered with. The header has the form:
Check the timestamp
Reject the request if
|now - t| exceeds the tolerance (300 seconds by default).
This prevents replay of an old capture.Recompute the HMAC
Compute
HMAC-SHA256 over the string `${t}.${rawBody}` using your per-key
whsec_… secret, hex-encoded.With the SDK
The official SDKs ship a verifier that does all four steps and returns the typed event.verifyWebhook / Webhook::verify accept an optional fourth argument to override the
300-second tolerance. They throw WebhookVerificationError /
WebhookVerificationException on a malformed header, stale timestamp, signature mismatch,
or invalid payload.Without the SDK
If you can’t use an SDK, the verification is short. Note the HMAC input is`${t}.${body}`.
Delivery, retries, and idempotency
- Link Skipper retries failed deliveries (non-
2xxor connection errors) with backoff, so your endpoint may receive the same event more than once. Treat handling as idempotent — key onjob_idand ignore an event you’ve already processed. - Always verify the signature before trusting any field in the body.
- Return a fast
2xx. If you need to do slow work, enqueue it and acknowledge immediately; a slow response counts as a failed delivery and is retried. - The webhook secret is
whsec_…, found in the dashboard next to your key. Rotate it there if it leaks. See Authentication.
Webhooks complement polling — they don’t replace it. If a webhook is never acknowledged
you can still read the final result from
GET /v1/jobs/{job_id}.