Errors & blockers

Preventing duplicate posts: a practical guide to idempotency keys

How accidental double-posts actually happen — retry storms, double-clicks, timeouts — and how a stable Idempotency-Key and its 72-hour window stop them at the API.

Quick answer

Duplicate posts almost always come from retries — a client timeout followed by a blind retry, a retry storm after a crash, or a double-click — not from business-logic bugs. The fix is making retries safe: send a stable Idempotency-Key per logical publish on POST /api/posts; a repeated key returns the original post instead of creating a second one, honored for 72 hours.

A launch announcement going out twice on a customer's Instagram and LinkedIn is not a cosmetic bug — it's the kind of mistake that gets escalated. Duplicate posts almost never come from a bug in your business logic; they come from a network timeout, a retry storm, or an impatient double-click triggering a second request for a job that was already accepted the first time. The fix isn't trying to prevent every retry — it's making retries safe.

PostGate handles OAuth, publishing and delivery evidence for every network through one API. Start free or read the docs.

01

How do duplicate posts actually happen?

  • Client-side timeout, blind retry. Your HTTP client gives up waiting for a response and retries the same call — but the original request may already have been accepted and queued. A client-side timeout only means you stopped waiting; it doesn't mean nothing happened.
  • Retry storms. A queue worker or job runner retries a failed publish attempt without checking whether a previous attempt already succeeded, especially after a crash mid-job.
  • Double-clicks and re-submits. A user clicks "Post now" twice, or a form re-submits after a page reload, firing two logically identical requests seconds apart.
02

How does an Idempotency-Key prevent duplicates?

PostGate accepts an Idempotency-Key header on every POST /api/posts call. If a request with that key has already been accepted, PostGate returns the original post instead of creating a second one — see Idempotency & safe retries. The key turns "did this already happen?" from a question your application has to answer into a question PostGate answers for you, on every retry, automatically.

Retry-safe post creationPostGate API
curl -X POST "https://api.postgate.studio/api/posts?profile_group_id=grp_acme123" \
  -H "Authorization: Bearer $POSTGATE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: launch-post-001" \
  -d '{
    "post": { "body": "Launch day. 🚀" },
    "profiles": ["pro_instagram123", "pro_linkedin456"],
    "media": ["https://cdn.example.com/launch.jpg"]
  }'
03

How should you generate the key?

A good idempotency key is stable for one logical publish and different for every distinct one — derived from your own internal job ID, queue message ID, or command ID, such as launch-post-{internal_job_id}. Generating a fresh random UUID on every retry defeats the entire mechanism: PostGate would see each attempt as a brand-new request and could publish more than once, which is the exact outcome you're trying to prevent.

04

How long does the key last, and what happens on a mismatch?

PostGate honors an idempotency key for 72 hours after it's first used. Retrying the identical request within that window returns the original post, unchanged — no new targets, no new attempts dispatched to any platform. Reusing the same key with different content — a different body, profiles, or media — is rejected with a 409 conflict instead of silently applying the new content, so a key collision can never quietly overwrite a post a customer already saw published.

Key reused with different contentPostGate API
{
  "error": {
    "code": "conflict",
    "message": "This idempotency key was already used with a different request.",
    "retryable": false,
    "request_id": "req_01JXYZ"
  }
}
05

Does your UI still need its own guardrail?

A stable key protects the request that ultimately reaches PostGate, but it won't stop a double-click from firing two separate requests with two separate keys if your frontend doesn't also disable the submit control after the first click. Combine both: disable the button (or debounce the action) on your side, and derive a stable key from the underlying job so that even a request that slips through gets deduplicated at the API.

Frequently asked questions

What causes duplicate social media posts?

Three patterns cover almost all of them: a client-side timeout followed by a blind retry — the original request may already have been accepted, since a timeout only means you stopped waiting; a retry storm, where a queue worker retries a failed publish without checking whether a previous attempt already succeeded; and double-clicks or form re-submits firing two logically identical requests seconds apart.

How should I generate an idempotency key?

Derive it from your own internal job ID, queue message ID, or command ID — for example launch-post-{internal_job_id} — so it stays stable for one logical publish and differs for every distinct one. Generating a fresh random UUID on every retry defeats the entire mechanism: each attempt looks like a brand-new request and can publish more than once, which is exactly the outcome you are trying to prevent.

What happens if I reuse an idempotency key with different content?

The request is rejected with a 409 conflict instead of silently applying the new content, so a key collision can never quietly overwrite a post a customer already saw published. Retrying the identical request within the 72-hour window returns the original post, unchanged — no new targets, no new attempts dispatched to any platform.

Do idempotency keys stop double-clicks in my UI?

Not on their own. A stable key protects the request that reaches the API, but a double-click can fire two separate requests with two separate keys if your frontend does not disable the submit control after the first click. Combine both layers: disable the button or debounce the action on your side, and derive a stable key from the underlying job so even a request that slips through gets deduplicated.

Build it with PostGate

One API for every social platform your customers care about — sandboxed, documented, and ready today.

Start building Read the docs