Designing a publishing integration that’s safe to retry
The three retry layers in a publishing pipeline — your client, PostGate’s internal delivery, and your webhook consumer — and what each one needs to be safe to retry at any layer.
Retry-safe publishing takes three separate layers: your client retries the create call with a stable Idempotency-Key so a retry returns the original post instead of a duplicate; PostGate retries provider delivery internally, so you shouldn't replicate that; and your webhook consumer deduplicates by event ID because deliveries can repeat. Retry only when the error says retryable: true.
"Just add retries" is easy advice and a genuinely dangerous default for a publishing integration, because a retry that isn't safe doesn't fail loudly — it succeeds twice. A retry-safe publishing integration isn't one trick; it's three separate layers, each retrying for a different reason, that all have to be built correctly for the system to behave the way a naive "it's idempotent" assumption implies.
PostGate handles OAuth, publishing and delivery evidence for every network through one API. Start free or read the docs.
What are the three layers, and what does each one do?
Retries happen at three points in a publishing pipeline, and conflating them is where most double-post bugs come from:
- Your client, retrying the create call. A timeout or dropped connection on
POST /api/posts— your job is to retry with the same request, not a fresh one. - PostGate, retrying provider delivery internally. A transient failure talking to Instagram, LinkedIn, or another platform — this happens automatically inside PostGate's own delivery pipeline, and you don't need to (and shouldn't try to) replicate it yourself.
- Your webhook consumer, being retried by PostGate. If your endpoint times out or returns a non-2xx status, PostGate retries the delivery — your job is to handle receiving the same event more than once.
Per Errors & retries: "PostGate already retries provider delivery internally; client retries are for API transport and acceptance failures." That one line is the whole division of responsibility — don't build client-side retry logic for platform-level failures PostGate is already handling.
Layer 1: how do you make create-call retries idempotent?
Send a stable Idempotency-Key, derived from your own job or command ID, on every POST /api/posts call. Reusing the key on retry returns the original post instead of creating a second one, for 72 hours after first use — see Idempotency & safe retries and, for the mechanics in more depth, Preventing duplicate posts.
Layer 2: when should you actually retry?
Every PostGate error returns a stable code and a retryable boolean — retry only when retryable is true, and apply exponential backoff with jitter when you do. rate_limited and platform_transient can be retryable; media_invalid, profile_disconnected, and platform_content_rejected need a real change or customer action, not another identical request. Don't recreate a failed target automatically — inspect the target's evidence first, per Errors & retries.
{
"error": {
"code": "platform_transient",
"message": "Instagram is temporarily unavailable.",
"platform": "instagram",
"retryable": true,
"details": {},
"request_id": "req_01JXYZ"
}
}Layer 3: how does a webhook consumer survive redelivery?
PostGate retries a webhook delivery whenever your endpoint times out or responds with a non-2xx status, so the same event can arrive more than once, and not necessarily in the order it occurred. Verify the signature, deduplicate by the event's stable id (also sent as X-PostGate-Event-Id), and acknowledge with a fast 2xx before doing real work — the full pattern is in Webhook retry best practices and Webhook signature verification, explained.
What happens when a layer runs out of retries?
No retry policy runs forever. On the delivery side, a target that never recovers ends up failed or skipped — a real, terminal, informative outcome, not a bug. On your webhook side, an event you genuinely cannot process after reasonable attempts belongs in a dead letter queue instead of being retried indefinitely — see Dead letter queues for webhook delivery. Both cases point back to the same source of truth if you suspect anything was missed: GET /api/posts/{id}, documented in Delivery lifecycle.
Frequently asked questions
Should my client retry when a post fails to reach Instagram or LinkedIn?
No — PostGate already retries provider delivery internally when a transient failure occurs talking to a platform, so you don't need to (and shouldn't try to) replicate it. Client retries are for API transport and acceptance failures: a timeout or dropped connection on the create call itself, retried with the same request and the same Idempotency-Key.
How long does an Idempotency-Key protect against duplicate posts?
Reusing the key on a retry returns the original post instead of creating a second one, for 72 hours after first use. Derive the key from your own job or command ID and send it on every POST /api/posts call, so a retried request is recognizably the same logical request.
Which publishing errors should never be retried?
Errors like media_invalid, profile_disconnected, and platform_content_rejected need a real change or customer action, not another identical request. Retry only when the error's retryable boolean is true — rate_limited and platform_transient can be — and apply exponential backoff with jitter when you do. Don't recreate a failed target automatically; inspect the target's evidence first.
What should happen when retries are exhausted?
On the delivery side, a target that never recovers ends up failed or skipped — a real, terminal, informative outcome, not a bug. On the webhook side, an event you genuinely cannot process after reasonable attempts belongs in a dead letter queue rather than infinite retry. In both cases, GET /api/posts/{id} is the source of truth if you suspect anything was missed.
