# Price the contract

Most integration defects don't come from choosing the wrong connection point. They come from not meeting the obligations that the chosen connection point imposes. This page calls those obligations the contract, and asks you to price it before you build.

Zen Electron's same-day delivery check is an API Extension. On every Cart update, it asks a postcode service whether the delivery address is in a metro area, then sets a Custom Field that the same-day Shipping Method depends on. It works in testing. Then, on the first morning in production, a customer's first Cart update of the day fails. By the end of this page, you can explain why (the answer is under [Runtime terms on Connect](/learning-integrate-with-commercetools/integration-essentials/price-the-contract.md#runtime-terms-on-connect)), and what the design should have stated up front.

## The one-sentence test

A design is finished when you can state each of the following in one sentence:

- **Latency budget**: how long the integration may take, and against which limit.
- **Idempotency strategy**: what happens when the same input arrives twice.
- **Failure behavior**: what happens when the integration, or something it depends on, is unavailable.

If any of the three needs a meeting to answer, the design isn't finished. The rest of this page lists what each connection point requires.

## API Extension contract

An API Extension runs inside the API request. Its response time adds directly to the request, and its failure fails the request.

### Time limits

The response limit includes connection time, network latency, and processing. Configure `timeoutInMs` for the expected work, then set a lower internal target so transient latency doesn't consume the entire budget. Check the current connection, response, and configurable maximum values in [API Extension time limits](/api/projects/api-extensions.md#time-limits).

### How commercetools handles the response

Your Extension responds in one of three valid ways. Any other response is treated as a failure.

```mermaid
flowchart TD
 A["Client sends a create or update request"] --> B["commercetools processes the request"]
 B --> C{"Does a trigger match?"}
 C -- "No" --> P["Save the resource"]
 C -- "Yes" --> D["Call the Extension and wait"]
 D --> E{"Extension response"}
 E -- "200 or 201 with update actions" --> F["Apply the actions, then save"]
 E -- "200 or 201 with no actions" --> P
 E -- "400 with errors" --> R["Reject the request<br/>and return the errors"]
 E -- "No response within the time limit" --> T["Fail with<br/>504 ExtensionNoResponse"]
 E -- "Any other status, or an invalid body" --> X["Fail with<br/>502 ExtensionBadResponse"]
 F -- "An action can't be applied" --> U["Fail with<br/>502 ExtensionUpdateActionsFailed"]

 style P fill:#d4edda,stroke:#155724
 style F fill:#d4edda,stroke:#155724
 style R fill:#f8d7da,stroke:#721c24
 style T fill:#f8d7da,stroke:#721c24
 style X fill:#f8d7da,stroke:#721c24
 style U fill:#f8d7da,stroke:#721c24
```

Two behaviors matter most for your design:

- **A failed Extension fails the customer's request.** commercetools doesn't retry an Extension within a request, as [Error cases](/api/projects/api-extensions.md#error-cases) describes, so only the next request calls the Extension again.
- **commercetools never fails open.** A timeout or bad response always fails the request. If you want the request to succeed when your own dependency is down, your Extension must decide that and respond accordingly.

### Choose a failure behavior

When a service your Extension depends on is unavailable, your Extension chooses between two behaviors:

- **Fail open**: approve the request without changes. The customer continues, but your rule isn't applied.
- **Fail closed**: reject the request. Your rule is enforced, but the customer is blocked.

The following responses come from Zen Electron's same-day delivery check, introduced at the start of this page. During Cart updates, the Extension asks an eligibility service whether the delivery postcode is in a metro area, then sets the `isMetroArea` Custom Field on the Cart so the same-day Shipping Method can apply. The first response is the normal case. The next two show what the Extension returns when the eligibility service is unavailable, one for each behavior.

In the normal case, the eligibility service confirms a metro postcode, and the Extension approves and sets the Custom Field:

```json title="Extension response: approve and set isMetroArea (200 OK)"
{
  "actions": [
    { "action": "setCustomField", "name": "isMetroArea", "value": true }
  ]
}
```

To fail open, the Extension approves with no changes. Either an empty body or an empty `actions` array means "no updates requested":

```json title="Extension response: fail open (200 OK)"
{
  "actions": []
}
```

To fail closed, the Extension rejects the request:

```json title="Extension response: fail closed (400 Bad Request)"
{
  "errors": [
    {
      "code": "InvalidInput",
      "message": "Same-day delivery eligibility can't be checked. Try again shortly.",
      "localizedMessage": {
        "en-AU": "Same-day delivery eligibility can't be checked. Try again shortly."
      }
    }
  ]
}
```

- For the `code` and `localizedMessage` fields, see [Validation failed](/api/projects/api-extensions.md#validation-failed).

If the Extension itself doesn't respond in time, the API client receives a `504` response with the [ExtensionNoResponse](/api/errors.md#extensionnoresponse) error code, whatever failure behavior you designed.

To monitor Extensions, use the response headers and the Platform Insights metrics and logs that [Observability](/api/projects/api-extensions.md#observability) describes.

> `setCustomField` works only if the Cart already uses a Type that defines `isMetroArea`, so make sure the storefront creates Carts with that Type. Don't use `setCustomType` in the Extension instead, because it replaces the Cart's Type and all of its existing Custom Fields.

### Choose where Extensions are worth the risk

Use risk to decide where an Extension belongs:

- **Lower risk**: back-office flows that aren't on the customer's critical path, such as enforcing data-quality rules before a Product is published.
- **Higher risk**: customer-facing flows, such as Cart updates and checkout. A slow or failing Extension directly affects conversion.

For a customer-facing Extension, apply the practices in [API Extensions performance tips](/api/performance-tips.md#api-extensions) and [Limits and error cases](/api/projects/api-extensions.md#limits-and-error-cases). Three matter most for the same-day delivery check, which runs on every Cart update: respond well inside the timeout, host the Extension in the same cloud Region as the Project, and avoid cold starts. Each one adds directly to the customer's request. Keep outbound calls to a minimum, and set their timeouts well under the Extension timeout.

## Subscription contract

A Subscription doesn't affect the original request. Its obligations fall on the consumer that reads the queue.

### Idempotency

Subscriptions use at-least-once delivery, so the same notification can arrive more than once. Your consumer must either recognize a notification it has already processed, or acknowledge before it acts and accept the cost described under [Acknowledgment timing](/learning-integrate-with-commercetools/integration-essentials/price-the-contract.md#acknowledgment-timing).

Choose the idempotency key for the notification type. [Subscription delivery guarantees](/api/projects/subscriptions.md#at-least-once-delivery) define the identifying fields for Messages and other notifications.

### Acknowledgment timing

When you acknowledge a notification decides which failure you accept for a side effect, such as sending an email:

- **Acknowledge before you act (at-most-once).** A failure after the acknowledgment doesn't cause a redelivery, so it can't repeat the effect. The effect is lost instead, because the queue doesn't redeliver an acknowledged notification. commercetools can't help here: its delivery guarantee ends when the notification reaches the queue.
- **Acknowledge after you act (at-least-once).** A failure is retried. A redelivery repeats the effect unless you deduplicate with the keys above.

Choose per effect. For the recommended policy for each email type, see [Choose delivery and deduplication behavior](/guides/connect-email-integration.md#choose-delivery-and-deduplication-behavior) in the email guide.

### Ordering

Notifications aren't guaranteed to arrive in order, especially after retries. Your consumer must be able to detect a notification that arrives ahead of an earlier one and defer it. The fields that make this possible differ between Messages and other notifications; see [No guarantee on order](/api/projects/subscriptions.md#no-guarantee-on-order).

### Delivery time

Delivery time isn't guaranteed, so a latency budget can't assume it; see [No guarantee on delivery time](/api/projects/subscriptions.md#no-guarantee-on-delivery-time). A flow that can't tolerate a delay of several minutes needs a periodic, idempotent reconciliation job alongside the Subscription.

### Retries and health

commercetools retries delivery based on the Subscription's health status:

- The health status decides the retry window; see [At-least-once delivery](/api/projects/subscriptions.md#at-least-once-delivery) and the [health endpoint](/api/projects/subscriptions.md#get-health-status-of-subscription-by-id). Set up automatic alerts for production queues.
- Configure a dead-letter queue on your message queue, so a message that fails on every attempt doesn't block the others.

### Payloads that don't fit the queue

A Message can arrive as [`payloadNotIncluded`](/search.md?urn=ctp:api:type:PayloadNotIncluded) when it doesn't fit the queue; the [delivery payload](/api/projects/subscriptions.md#messagedeliverypayload) reference defines when.

Your consumer has two ways to recover the content:

- **Retrieve the full Message** from the [Messages API](/api/projects/messages.md), if Messages are enabled for the Project.
- **Retrieve the resource.** The resource returns its current state, which may be newer than the Message. Compare its `version` with the Message's `resourceVersion` before you act on it.

### Avoid loops

If your consumer writes back to the resource it was notified about, that write can trigger another notification. Filter out changes your own integration made, for example by checking who made the change or by comparing the values before writing.

## Other contracts

The following obligations are recommended practice. They come from how each pattern behaves, not from a platform limit.

| Pattern | Obligations |
| --- | --- |
| Inbound endpoint | Authenticate the caller. Validate the payload. Upsert by a stable key, so a repeated webhook has no extra effect. |
| Scheduled job | Prevent overlapping runs. Save checkpoints, so a restarted job resumes instead of starting over. Make each unit of work idempotent. |
| Import API | Accept eventual consistency. Monitor ImportOperation states. Make sure referenced resources exist within the documented operation lifetime. |

## Runtime terms on Connect

If you run the integration on Connect, the runtime adds acknowledgment, request-timeout, retention, and environment terms. Use [Application behavior and limitations](/connect/deployment-behavior-and-environments.md) for the current values. A Managed API Extension on the Commerce Integration Layer still runs inside the write, so the platform API Extension limits apply to it too; see [Limits](/learning-integrate-with-commercetools/commerce-integration-layer/limits.md#managed-api-extensions).

These terms explain Zen Electron's failed Cart update. The eligibility Extension ran on a sandbox Deployment that had scaled to zero. The cold start exceeded the Extension response limit, so commercetools returned `504 ExtensionNoResponse`. A `production` Deployment keeps warm instances ready, and [Choose where it runs and how to adopt it](/learning-integrate-with-commercetools/integration-essentials/choose-where-it-runs.md) covers when it's available.

## Worked example: price two contracts

Zen Electron's team writes the contract for two integrations before building them. The figures are the team's design choices, not platform values.

**Same-day metro delivery eligibility (API Extension)**

- **Latency budget**: respond within 300 ms, against a `timeoutInMs` of 1,000 ms, with outbound calls to the postcode service limited to 200 ms.
- **Idempotency**: the Extension sets `isMetroArea` from the current address, so repeating the call produces the same result.
- **Failure behavior**: if the postcode service is unavailable, fail open by approving with no actions. `isMetroArea` stays unset, so same-day delivery isn't offered, but the customer can still check out with other Shipping Methods. Run the Extension on a production Deployment or always-warm infrastructure in the Project's Region.

**Order confirmation email (Subscription)**

- **Latency budget**: acknowledge within the Connect event acknowledgment deadline, and aim to send within 5 minutes of the Order in normal conditions. Delivery time isn't guaranteed, so a delay of several minutes isn't a defect.
- **Idempotency**: acknowledge before sending, so a failed send doesn't lead to a redelivery and a second attempt. A duplicate is still possible if the acknowledgment doesn't reach the queue in time, and the team accepts that as rare. Sending an email can't be undone, and the team prefers a missed email to a duplicate.
- **Failure behavior**: if the email provider is down or the send fails after the acknowledgment, the email is dropped, because the queue doesn't redeliver an acknowledged notification. The team accepts that for order confirmations and watches the provider's activity log for missing sends. An email where a miss blocks the customer, such as password reset, needs its own decision.

## Key takeaways

- A design is finished when you can state its latency budget, idempotency strategy, and failure behavior in one sentence each.
- An API Extension must respond within its configured platform limit. commercetools never retries it within a request and never fails open.
- Failing open is your Extension's choice: approve with no actions when your dependency is down.
- Subscription consumers must be idempotent (`resource.id` with `sequenceNumber` or `version`), tolerate out-of-order delivery, and pair with reconciliation for time-critical flows.
- Acknowledging before a side effect avoids duplicates but loses the effect if it then fails. Acknowledging after it retries failures but needs deduplication. Choose per effect.
- Handle `payloadNotIncluded`, and compare versions if you retrieve the current resource.
- Sandbox Deployments on Connect scale to zero, so their cold starts can exceed the response budget of a customer-facing Extension.

## Related pages

- [Area overview page with navigation](/learning-integrate-with-commercetools.md)
- [Previous page: Choose a connection point](/learning-integrate-with-commercetools/integration-essentials/choose-a-connection-point.md)
- [Next page: Choose where it runs and how to adopt it](/learning-integrate-with-commercetools/integration-essentials/choose-where-it-runs.md)
- [Search documentation and API specs](/search.md)