The receiving desk counts 120 finished garments against one delivery line. The integration sends that receipt to the apparel ERP. The stock balance rises from 380 to 500 pieces.

A network fault hides the reply. The sender tries again, and a weak interface adds another 120 pieces. The screen now shows 620, while the warehouse still holds 500.

The second message was real. The second receipt was not.

With idempotency, sending the same request twice has the effect of sending it once. For inventory software, the promise is specific. The same approved receipt must not create a second stock movement.

This guide develops an original reference design for receipts and transfers. Its quantities, IDs, tables, and failure tests are hypothetical. It explains what the server must remember, what a retry may change, and how to prove the final stock effect.

Define the business effect before the API

Begin with the physical event. A receipt adds an agreed quantity of an item to a stock location. An approved source document supports that action.

For apparel, the item may be a style–colour–size variant. Fabric may need a roll and lot. Trims may need a pack unit and a base-unit conversion. These details belong to the business instruction.

Write the effect in a sentence. For example: “Post delivery line GR-104-2 as 120 pieces of variant V-Blue-M into location FG-01.”

Then define what must happen once. It may include a ledger entry, an inventory balance update, and a receipt reference. The system may also print a label or send a notice. Those effects need their own controls.

ApparelDiary's apparel ERP checklist covers wider system selection. Here, the focus is the receiving API. What should it do when the same instruction arrives again?

Distinguish a retry from a second receipt

Two receipts can have identical quantities and item details. That does not make them duplicates.

The warehouse might receive 120 blue medium shirts on Monday. Another 120 arrive on Tuesday. Each delivery has its own source line and operation ID. Both must affect stock.

A retry belongs to the same original business instruction. It reuses the original operation ID and approved content. It does not invent a fresh ID merely because the last attempt timed out.

AWS describes caller-provided request identifiers as a way to express that intent. Equal parameters do not prove that the caller meant to repeat an earlier request. AWS on safe retries

Use content comparison to detect a changed instruction under the same ID. Do not use it as the sole test for whether two legitimate receipts are the same event.

HTTP method names are only part of the contract

HTTP defines idempotent methods by their intended effect. RFC 9110 includes PUT, DELETE, and safe methods in that group. A repeated response need not be byte-for-byte identical. HTTP semantics, section 9.2.2

A receipt-creation endpoint may use POST. Its safety then depends on the documented application contract. A client can add an Idempotency-Key header. That helps only if the server implements the promised behaviour.

Read the target API's exact version and rules. Do not assume all vendors store failures, retain keys, or handle concurrent calls the same way.

Stripe provides a useful documented example of such differences. Its current guidance separates API v1 and v2 retry behaviour. That is a reason to check a contract, not a template to impose on an unrelated ERP. Stripe idempotent requests

Specify the operation identity

The reference design uses a composite identity: CompanyID, OperationType, and OperationID.

CompanyID comes from the authenticated business context. OperationType distinguishes receipt creation from another kind of action. OperationID is created once by the source when the instruction becomes ready to send.

Save that ID at the source before the first network attempt. If the sender crashes and restarts, it must recover the same ID.

Do not use a person's name, email address, or a mutable item description as the key. Use a stable opaque identifier and keep business references in separate fields.

A source receipt line remains useful even with an operation ID. The source might create a fresh ID for an already-posted receipt by mistake. The receipt line helps the receiver detect that error.

Define the scope of that business uniqueness rule. A document can have several valid lines and several permitted partial receipts.

Write a small receipt contract

Field

Example

Purpose

CompanyID

C01

Business scope, validated against caller

OperationType

ReceiveFinishedGoods

Names the requested action

OperationID

OP-8421

Stable identity across attempts

SourceReceiptLine

GR-104-2

Physical receipt reference

ItemVariantID

V-Blue-M

Exact style, colour, and size identity

LocationID

FG-01

Stock destination

Quantity

120

Amount in the stated unit

Unit

PCS

Prevents pieces and packs being confused

ContractVersion

1

Defines interpretation rules

This is a proposed contract, not a documented endpoint for a named ERP.

Validate required fields before applying stock. Reject an unknown variant or an unsupported unit rather than guessing.

If the source sends ten packs of twelve pieces, choose where conversion occurs. Store the original unit and the resolved base quantity where the design needs both. A retry must resolve to the same approved meaning.

Do not let a changed pack size silently alter an old instruction during replay. Retain the conversion used for the original posting.

Bind the identity to the approved content

Keep the relevant request content with the operation record. A carefully defined fingerprint can also serve the comparison.

The comparison must include fields that change the business effect. Quantity, unit, item, lot, location, and source reference are examples. A gateway may add a logging timestamp. That should not turn an unchanged receipt into a new instruction.

Define normalization before hashing. The strings “120” and “120.0” may mean the same numeric quantity under the contract. Reordered JSON fields should not accidentally change meaning.

Avoid informal rules such as removing every space from every field. A space can be meaningful inside an external code.

In this design, the server keeps the normalized request. It also keeps the contract version. A repeat with equal content can recover the recorded result. A repeat with changed content produces a clear conflict and no new stock effect.

Make changed-key reuse visible

Suppose OP-8421 first requests 120 pieces. A user then changes the quantity to 118 and sends OP-8421 again.

The server could return the old success and hide the mismatch. The two systems would then tell different stories. Applying the new quantity as another receipt would be worse.

The reference contract rejects the mismatch. The response names the operation and fields in conflict. It must not expose another company's data.

The source must then decide what the business intended. The first receipt may have been correct. If two pieces were rejected later, create an authorized adjustment. If the original instruction never posted, use the supported correction route.

Do not solve every mismatch by generating a fresh ID. That can turn an editing error into a second legitimate-looking stock movement.

Give the operator a link to the recorded receipt and a clear next action.

Use durable uniqueness at the receiver

A “check whether the key exists” query is not enough by itself. Two requests can perform the check before either inserts the key.

Enforce uniqueness in the database that owns the operation record. PostgreSQL documents unique constraints across a group of columns. It also explains that a primary key combines uniqueness with non-null values. PostgreSQL constraints

For this reference design, the operation table's key is:

PRIMARY KEY (company_id, operation_type, operation_id)

Each part must be present. The chosen database's null and conflict rules must match the intended contract.

Use a supported insert-and-conflict flow or equivalent mechanism. On a conflict, read the existing operation under the documented transaction rules. Compare content before returning its outcome.

Do not catch every database error and label it “duplicate.” A connection failure, invalid foreign key, or storage error needs different handling.

Commit the operation and stock effect together

The simplest reference boundary is one database transaction owned by the receiving service. Within it, the service records the operation. It writes the inventory movement and saves the result.

Either all required writes commit or none do. PostgreSQL's tutorial explains this all-or-nothing property. It also describes BEGIN, COMMIT, and ROLLBACK. Transaction basics

The following is a logical sequence, not production-ready SQL:

Begin transaction.
Claim the new operation identity.
Validate the approved business instruction.
Write one receipt ledger movement.
Update stock under the database's concurrency rules.
Store the receipt reference and completed result.
Commit transaction.
Return the recorded result.

Check for an existing identity. If one is found, follow the replay branch. Do not post another movement.

This design assumes the service can control that transaction boundary. It does not authorize direct writes into a vendor ERP's private tables.

Understand the external ERP boundary

An adapter may own its own database while calling a remote ERP API. A local adapter transaction cannot make that remote call part of the same local commit.

The adapter can record that it sent a request. The ERP still needs to support safe posting and lookup. It may use a stable external reference or its own idempotency contract. Another proven conditional-write method may also work.

The ERP may post just before the adapter crashes. If the adapter has not recorded success, the result remains uncertain. An adapter cache cannot remove that uncertainty.

Mark the operation as unresolved and query the authoritative target where possible. If the target cannot identify the posting reliably, route it for controlled reconciliation.

This is a system limitation to discover during interface design. Do not hide it behind a claim that middleware provides “exactly once.”

Use the vendor's supported extension or API. The target's inventory rules remain part of the proof.

Walk through the receipt ledger

Start with 380 pieces at FG-01. One valid receipt adds 120.

Closing quantity = Opening quantity + Valid receipts − Valid issues.

Closing quantity = 380 + 120 − 0 = 500 pieces.

Now deliver the same operation three times. The expected receipt ledger contains one movement for 120 pieces. The attempt log can contain three records.

Attempt

Server finding

Stock effect

First

New valid operation

+120 pieces

Second

Same identity and content

0

Third

Same identity and content

0

Total

One posted receipt

+120 pieces

The correct closing balance remains 500.

A receiver that posts twice reaches 620. Its overstatement is 120 pieces. A receiver that posts all three attempts reaches 740, overstating stock by 240.

These calculations describe physical quantity only. Inventory valuation and accounting entries require their own defined rules and checks.

Test the awkward moment after commit

The most revealing test stops the response after the transaction commits. The caller sees a timeout even though stock has changed.

Send the same operation again. The server should find the completed record and return its receipt reference. It must not add another ledger movement.

Also test the opposite point: stop the process before commit. After recovery, check the operation. It should be absent or in a defined state from which work can resume. A retry must not leave half a receipt.

AWS discusses the need to record the request token and related mutations atomically. Its account also describes a useful replay result. It tells the caller which prior operation succeeded. AWS atomic operation and replay design

Keep evidence from both sides of the test. A successful HTTP reply alone does not prove the stock ledger is correct.

Keep a replay result separate from live stock

Apparel ERP integration and duplicate prevention

A saved reply describes the receipt that posted. It may not describe stock now.

Return to the 500-piece balance. Later, the warehouse issues 40 pieces to packing. Stock at that location is now 460. The client then retries the old 120-piece receipt after losing its first reply.

The server should return the same receipt reference and posted amount. It must not post again. It should also avoid presenting the old 500-piece balance as a fresh stock count.

Use clear response fields. “Receipt quantity: 120” describes the event. “Stock after posting: 500, at 10:15” is a dated result. “Current stock” needs a separate, current read with its own time and scope.

This distinction matters at the desk. An operator may see a reply arrive now and assume every value in it is current. The interface should say that the receipt was already posted, then refresh the stock view if needed.

Test this case with an issue between the first receipt and its replay. The closing stock must stay 460. The receipt still shows 120 pieces. Both values are correct because they describe different things.

Handle two simultaneous attempts

Send two copies of OP-8421 at nearly the same time. Both carry the same 120-piece instruction.

Only one transaction may create the stock effect. The other may wait for the first. It may instead receive an in-progress result or recover the final outcome. The API contract should say which behaviour clients can expect.

If the second call returns before completion, distinguish “accepted” from “posted.” Give it an operation reference that can be checked.

Do not let an expired processing lease create two active posters. Workers may resume or take over unfinished work. Protect the final stock write against a worker whose ownership has expired.

A small service may complete the work in one call. One local transaction is then often easier to reason about. Long-running work may need a more elaborate state machine. Add that complexity when the work needs it. A queue's availability is not enough reason.

Treat transfers as one stock instruction

A transfer moves quantity between locations. In the hypothetical case, 80 pieces move from FG-01 to PACK-02.

Before transfer, FG-01 holds 500 and PACK-02 holds 200. After a valid transfer:

FG-01 closing quantity = 500 − 80 = 420 pieces.

PACK-02 closing quantity = 200 + 80 = 280 pieces.

Total before = 500 + 200 = 700 pieces.

Total after = 420 + 280 = 700 pieces.

Under this simple design, the debit and credit belong to one transfer operation. A retry must not repeat either side.

A real system may include in-transit stock, ownership changes, or quality status. Model those states explicitly. Do not force a multi-stage physical transfer into a two-location example.

Check location balances as well as the total. A duplicated transfer may leave the company-wide total unchanged. Stock can still end up in the wrong places.

Keep corrections separate from retries

The warehouse has received 120 pieces. A later approved inspection finds two damaged pieces. They must leave available stock.

That is new business information. It needs a new adjustment linked to the original receipt. Give it its own reason and authorization.

If the adjustment reduces this example's total stock, the quantity becomes:

Adjusted quantity = 500 − 2 = 498 pieces.

The pieces might instead move to a quality-hold location. Total stock may then stay 500, while available stock becomes 498. Define which measure changes.

Do not overwrite the old receipt content and reuse its key. Preserve the original posting and the later correction.

A correction can itself be delivered more than once. It needs the same retry protection as the receipt it corrects.

Publish downstream events without a write gap

The receipt may need to notify planning or a warehouse system. A separate “save stock, then send message” sequence can fail between its two steps.

A transactional outbox keeps an outgoing event beside the business change. Both writes share the supported transaction boundary. A separate publisher sends the saved event later.

Microsoft's Dataverse architecture describes this pattern and its at-least-once delivery. It also states that consumers need idempotent processing because duplicate messages can occur. Transactional outbox guidance

In the receipt example, the outbox event has its own stable EventID and references OP-8421. Marking it sent is separate from whether the receipt exists.

If the publisher sends an event and crashes before recording delivery, it may send again. The downstream consumer must recognize that event. The outbox closes a write gap; it does not remove every duplicate from the network.

Set retry limits and retention together

A safe retry still consumes resources. Use timeouts, bounded attempts, and a backoff policy suited to the service.

AWS explains how retries can add load to a failing service. Backoff spaces attempts, while jitter spreads callers that would otherwise retry together. Timeouts and backoff

Do not retry a known validation failure unchanged. An unknown item code needs correction, not a faster loop.

Keep operation records while replay remains possible. Include delayed jobs and recovery from backup in that decision. Define what happens after that period.

A short key cache is not enough if an old receipt can return months later. The durable source receipt reference may provide a second uniqueness check.

Vendor rules vary. Stripe's API v1 documentation permits key pruning after at least twenty-four hours. Reusing a pruned key can then trigger new processing. Treat that as a vendor-specific contract, not a universal inventory retention rule. Stripe retention behaviour

Reconcile business records rather than HTTP counts

A report of one hundred successful calls does not prove that one hundred receipts posted. Some calls may be replays of the same receipt.

Reconcile distinct source operations to distinct target receipt references. Compare item, location, unit, quantity, and final state.

Keep unresolved operations in a visible queue with their age and last evidence. Separate them from confirmed rejections and completed work.

The worked receipt starts with one source receipt line. It should have one operation record and one receipt reference. The ledger should show one 120-piece movement. Three attempts do not change that relationship.

Record replay counts for technical monitoring. A sudden rise may reveal a slow endpoint or lost replies. It should not inflate the warehouse's received-quantity KPI.

Allow partial receipts without weakening duplicate checks

A purchase-order line for 500 shirts may arrive in several deliveries. Suppose the first receipt contains 120 pieces and the second contains 80. Both receipts point to one purchase-order line. Each receipt line still needs its own stable identity.

The valid received quantity is 200 pieces. Under this simple example, 300 remain to arrive.

The receiver might enforce uniqueness on the purchase-order line alone. That rule could reject the second legitimate delivery. If it uses no business reference, a new operation ID can disguise a duplicate of the first delivery. Neither rule fits the physical process.

Use the source's receipt-event identity at the right level. Preserve the purchase-order line as a link, not as the sole duplicate key. Where the source lacks a stable receipt ID, resolve that gap before automated posting.

Returns and cancellations also need their own event identities. A later return does not make the original receipt disappear. It changes the stock story through another recorded action.

During acceptance testing, send both partial receipts, then replay each one. The final received quantity must remain 200. There should be two receipt references and two ledger movements. Attempt counts may exceed two. The business record should explain why only two movements exist.

Build an acceptance test matrix

Test

Required observation

Same key and content repeated

One receipt and one stock effect

Same key with changed quantity

Clear conflict; no second effect

Different valid receipts with equal content

Both post under distinct identities

Two concurrent copies

One posting despite overlap

Crash before commit

No partial stock effect

Lost reply after commit

Replay recovers the recorded result

Old source receipt with a new key

Business-reference rule handles it

Duplicate outbox event

Consumer applies its effect once

Use realistic variants, lots, and units in the test data. A generic item called “A” will not reveal a style–colour–size mapping error.

Check the ledger and balances after each test. Keep request IDs and target references in the test evidence.

These are proposed acceptance cases. A demonstration may pass these cases. Still test the configured production interface and its recovery path.

Common questions

Is an idempotency key a stock authorization?

No. The caller still needs permission and the instruction must pass business validation. A familiar key is not permission to read another company's result.

Can I use the item and quantity as the key?

That can suppress legitimate repeated receipts. Use an operation identity and retain the source business reference.

Does a timeout mean the receipt failed?

No. It means the caller did not receive a usable answer. Check the recorded operation or retry only under a proven safe contract.

Can a cache provide the whole solution?

A cache can speed lookups. The operation and stock records must survive a crash. Keep them for the required replay period.

Prove the effect where stock is owned

The desk receives 120 pieces. That should create one 120-piece receipt, even if the message arrives several times. That is the concrete test.

Keep a stable operation identity and bind it to the approved content. Enforce the posting rule where the stock records are owned. Record enough evidence to recover a lost reply. Then test failures around the commit boundary and reconcile the ledger. The result is an interface whose retries can be explained in warehouse terms.

Duplicate postings show up as stock and ledger differences, so see ERP for the apparel industry, cycle counting, material issue and return controls and month-end close.

References