Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/integrations/integration-toolkit/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,32 @@ curl -X POST 'https://integration-toolkit.sls.epilot.io/v1/integrations/{integra
- `secure_proxy` — Route requests through epilot's secure proxy for static IP or VPN access. See [Secure Proxy Use Cases](#secure-proxy-use-cases).
:::

### Inbound Use Case Options

Beyond the mapping itself, an inbound use case's `configuration` accepts the following options.

| Option | Values | Default | Description |
|--------|--------|---------|-------------|
| `direct` | boolean | `false` | Skip the mapping engine and accept entity-shaped payloads. See [Direct Mode](./inbound/direct-mode.md). |
| `interactive` | `disabled`, `on_request`, `always` | `disabled` | Whether events of this use case may run on the interactive lane. See [Interactive Registration](./interactive-registration.md#enabling-it-on-the-use-case). |

```json title="Allow the request to decide"
{
"entities": [ "..." ],
"interactive": "on_request"
}
```

With `on_request` — the normal setting — one mapping serves both the nightly batch and the
registration push, and the inbound request decides which it is by sending `interactive: true`. With
`disabled`, a request that asks for interactive is processed as bulk and told so per event; nothing
is rejected. With `always`, every event of the use case is interactive.

If a caller is going to wait on your use cases by slug (`wait_for_use_cases`), model one inbound use
case per partner event type and give every use case a slug: a use case that maps several kinds of
message cannot tell them apart, and one without a slug cannot be named at all. See
[Interactive Registration](./interactive-registration.md#one-use-case-per-partner-event-type).

### Enabling/Disabling a Use Case

```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,15 @@ Events flow through the following pipeline:

![Processing Flow](/img/integrations/toolkit-processing.svg)

Ingestion is asynchronous: the response means "accepted and queued", not "written". When an end
customer is waiting for the push to land — portal self-registration, adding a contract — send the
portal's correlation id with the request and let the portal wait on it. See
[Interactive Registration](../interactive-registration.md).

## Next Steps

- [Entity Mapping](./mapping.md) - Learn about field mappings and transformations
- [Unique Identifiers](./unique-identifiers.md) - Configure entity lookup strategies
- [Relations](./relations.md) - Link entities together
- [Interactive Registration](../interactive-registration.md) - Push data an end customer is waiting for
- [Examples](./examples.md) - See complete integration examples
931 changes: 931 additions & 0 deletions docs/integrations/integration-toolkit/interactive-registration.md

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion docs/integrations/integration-toolkit/monitoring/codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ Every monitoring event carries a **code** and a **level**. The code says what
happened; the level says how much you should care. Both are filterable in the
Integration Hub's [Monitoring tab](./overview.md) and through the events API.

There are 85 codes. You are most likely here because you saw one in a failed
There are 98 codes. You are most likely here because you saw one in a failed
event — find it below.

:::tip
A code's level is fixed. That is what makes alert rules scoped to `_error_` or
`_warning_` predictable — see [Alerting](./alerting.md#choosing-what-to-watch).
:::

The interactive lane codes (`INTERACTIVE_*` and `CORRELATION_*`) are explained in
context, with when each is raised, in [Interactive Registration](../interactive-registration.md#monitoring).

## Error

Something failed and the event did not do what it was meant to do. These are what the default alert rules watch.
Expand All @@ -32,6 +35,8 @@ Something failed and the event did not do what it was meant to do. These are wha
| `ATTACHMENT_NOT_FOUND` | The file no longer exists — it was removed between the event and the delivery |
| `ATTRIBUTE_TYPE_MISMATCH` | An attribute value did not match the type declared in the entity schema |
| `CONDITIONAL_VARIANT_WRITE_FAILED` | Conditional pricing refused one variant write. The item is dropped and not retried; the rest of the batch and the run continue. details.code names the reason where pricing gave one — UNKNOWN_ERROR means it did not |
| `CORRELATION_ID_MISSING` | A request asked for something that only makes sense for a correlation, but did not say which correlation |
| `CORRELATION_REQUIREMENT_FAILED` | Something a caller was waiting for can no longer arrive: every event that would have satisfied the requirement ended in an error |
| `DEPRECATED_ENDPOINT` | This endpoint version is deprecated |
| `DIRECT_ENTITY_NOT_ALLOWED` | The entity is not permitted by the use case entity allowlist |
| `DIRECT_PAYLOAD_INVALID` | The direct mode payload failed validation against the versioned payload schema |
Expand Down Expand Up @@ -96,8 +101,16 @@ Processing continued, but something needs a human eye — often a retry in fligh
|---|---|
| `ACK_TIMEOUT` | Acknowledgement timed out waiting for the ERP system |
| `CONDITIONAL_VARIANT_WRITE_WARNING` | One variant write succeeded with a warning. The variant is stored and the rest of the batch and the run continue. details.code names the warning |
| `CORRELATION_CLOSED_INCOMPLETE` | The partner declared a correlation complete and everything it sent was processed, but what the caller was waiting for was not in it — see `missing_entities` and `missing_use_cases`. Reported once per correlation |
| `CORRELATION_CLOSE_IGNORED` | The partner declared a correlation complete, but the close was not applied: either the same request has to be retried, or none of its events was processed as interactive (the use case does not allow it, or the request did not ask for it). Retry with `correlation_complete`, or send the events with `interactive: true` |
| `CORRELATION_EVENT_LIMIT_EXCEEDED` | This correlation already holds as many events as one correlation may, so further events sent under it were processed normally and cannot be waited on. A correlation is meant to cover one registration or one change; this usually means the partner sends the same correlation id with every push. Each request that should be waited on needs its own id |
| `CORRELATION_LATE_EVENT` | An event arrived under a correlation the partner had already declared complete. It was processed, but anyone waiting on that correlation had already been answered — the partner is sending `correlation_complete` too early |
| `CORRELATION_WAIT_REJECTED` | A caller tried to wait on a correlation while too many waits were already open, so it was asked to retry. Its data is unaffected — only the answer it was waiting for is slower |
| `CORRELATION_WAIT_TIMEOUT` | A caller waited on a correlation until its time ran out without getting what it asked for — see `missing_entities` and `missing_use_cases` for what had not arrived. Reported once per correlation |
| `EXTERNAL_WARNING` | A warning span pushed by an external system via the external monitoring events endpoint. |
| `FILE_PROXY_UPLOAD_RETRYING` | A file upload failed with a retryable error and will be retried automatically — one event per attempt |
| `INTERACTIVE_CROSS_LANE_WRITE` | The same entity was updated from two different processing lanes within seconds of each other, one of them the interactive lane. Both updates were applied; this reports that they overlapped, so the order they landed in was decided by timing |
| `INTERACTIVE_RATE_LIMITED` | The organization asked for interactive processing more often than its agreed rate, so these events were processed on its normal lane instead. Nothing was lost and the correlation can still be waited on — it will simply take as long as a bulk event does |
| `LOOKUP_UNMAPPED` | A value was not listed in a lookup table and its fallback was used — see lookup_name and lookup_key for the gap |
| `MSG_LATE_ARRIVAL` | An event arrived after the poll consumer had already received later events, so it was placed at the end of the stream instead of at its event time — it is delivered, but out of event-time order |
| `SOFT_DELETED_ENTITY_MATCHED` | A soft-deleted entity matched the unique ID — it will be resurrected on upsert, or referenced as-is by a relation. Investigate why the ERP source is sending events for a deleted entity. |
Expand Down Expand Up @@ -130,10 +143,13 @@ Lifecycle markers rather than outcomes: a message was queued, a duplicate was ig
| Code | What it means |
|---|---|
| `ACK_PENDING` | Acknowledgement is pending from the ERP system |
| `CORRELATION_CLOSED_EMPTY` | The partner declared a correlation complete without sending any event for it. That is the partner reporting "I found nothing", and anyone waiting is told so at once instead of waiting it out |
| `CORRELATION_COMPLETED` | Every event the partner sent under this correlation has finished processing and the partner declared the correlation complete |
| `DUPLICATE_EVENT` | This event was already processed (duplicate) |
| `EXTERNAL_INFO` | An informational span pushed by an external system via the external monitoring events endpoint. |
| `FAN_OUT_EMPTY` | The split expression returned an empty list, so nothing was sent — expected for events that carry no relevant items |
| `FILE_PROXY_UPLOAD_ENQUEUED` | A per-file upload was accepted for delivery during fan-out |
| `INTERACTIVE_NOT_ALLOWED` | Interactive processing was requested for a use case that is not configured for it, so the events were processed normally. Set `interactive` to `on_request` or `always` on the use case if the caller is meant to be able to wait for these events |
| `MSG_ENQUEUED` | Outbound message enqueued to the poll queue, awaiting consumption by the ERP. When the use case maps the payload, the detail carries the mapped payload as delivered and its mapping_version (or payload_omitted when it exceeds 48 KiB) |
## Status-code families

Expand Down
3 changes: 3 additions & 0 deletions docs/integrations/integration-toolkit/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The Integration Toolkit is composed of the following components. Each plays a sp
| **[ERP Integration API](#erp-integration-api)** | CRUD API to manage integrations, use cases, and mappings | Stable |
| **[ERP Inbound API](#inbound-api)** | Dedicated API to receive and simulate inbound ERP events | Stable |
| **[Use Cases](./use-cases.md)** | Documented integration flows with testing support | Stable |
| **[Interactive Registration](./interactive-registration.md)** | Correlation-id contract and a dedicated lane for inbound pushes an end customer is waiting for | Rolling out |
| **[Core Entities](/docs/entities/core-entities)** | Standardized entity schemas for mapping targets | Stable |
| **[Changesets](/docs/entities/changesets)** | Pending attribute updates that wait for ERP confirmation or human approval | Stable |
| **[Core Events](/docs/integrations/core-events)** | Standardized event payloads for outbound notifications | Stable |
Expand Down Expand Up @@ -160,6 +161,8 @@ Push data from your ERP into epilot. Typical flows:

[Inbound Integration Guide](./inbound/getting-started.md)

Inbound ingestion is asynchronous by design. For the flows where an end customer is waiting for the push to land — portal self-registration, adding a contract — [Interactive Registration](./interactive-registration.md) adds a correlation-id contract, a dedicated processing lane and status endpoints the portal waits on, so nobody has to guess whether the data is still coming.

### Outbound (epilot to ERP)

Push epilot events to your ERP via webhooks. Typical flows:
Expand Down
35 changes: 35 additions & 0 deletions docs/integrations/integration-toolkit/use-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Most integrations start with inbound use cases (syncing master data into epilot)
| [Sync Billing Events](#sync-billing-events) | [`billing_event`](/docs/entities/core-entities#billing_event), [`billing_account`](/docs/entities/core-entities#billing_account) | Invoices/payments posted in ERP |
| [Sync Installment Schedule](#sync-installment-schedule) | [`billing_event`](/docs/entities/core-entities#billing_event), [`billing_account`](/docs/entities/core-entities#billing_account) | Installment plan (Abschlagsplan) created/changed in ERP |
| [Sync Portal User](#sync-portal-user) | [`portal_user`](/docs/entities/core-entities#portal_user), [`contact`](/docs/entities/core-entities#contact) | Portal user registered or mapped |
| [Interactive Registration](#interactive-registration) | [`contact`](/docs/entities/core-entities#contact), [`contract`](/docs/entities/core-entities#contract), [`billing_account`](/docs/entities/core-entities#billing_account) | Portal self-registration or contract addition, while the end customer waits |

### Outbound (epilot to ERP)

Expand Down Expand Up @@ -340,6 +341,40 @@ This use case is typically combined with [Keep Customer In Sync](#keep-customer-

---

### Interactive Registration

Deliver the customer's data while the person is still waiting for it: portal self-registration, adding a contract or a customer account to an existing login, and public journeys that identify a customer. The difference to every other inbound use case is that someone is watching a spinner.

```mermaid
flowchart LR
U[End customer] -->|Identifiers| EP[epilot portal]
EP -->|Extension hook + correlation id| MW[Middle Layer]
MW -->|Validate| ERP[ERP System]
MW -->|POST /v3/erp/updates/events<br/>correlation_id, interactive| API[ERP Integration API]
EP -->|Wait on the correlation| API
API -->|Upsert| E[contact, billing_account, contract, meter]
```

**What happens in epilot:**
- The portal mints a correlation id and passes it to your [extension hook](/docs/apps/components/portal-extension) together with the identifiers the end customer entered
- Instead of polling entity search for data that may or may not be coming, the portal waits on that correlation id until the entities it needs exist — and the first matching entity is enough, so further data for the same customer may still be arriving
- On success it links the contact and shows the contracts; on a closed correlation that never produced a contract it says so, instead of timing out silently

**What happens in the ERP:**
- Middle layer validates the identifiers in the ERP and answers the hook as it does today — or, when the customer cannot be found, with an error, pushing nothing
- Middle layer pushes the customer's data to `/v3/erp/updates/events` with the portal's `correlation_id` and `interactive: true` — the only two fields required — reusing the existing inbound mappings. The id is the one the hook passed, unique to this identification; a fixed or constant correlation id sent today must be replaced by it
- Strongly recommended: close the correlation with `correlation_complete: true`, the only way the portal learns that nothing more is coming — including when the ERP has the customer but not the whole bundle. Pushing one request after another, the last request carries it; pushing in parallel, it is a request of its own with no events, sent once every push was accepted

**Core Entities:** [`contact`](/docs/entities/core-entities#contact), [`billing_account`](/docs/entities/core-entities#billing_account), [`contract`](/docs/entities/core-entities#contract), [`meter`](/docs/entities/core-entities#meter)

**Typical unique identifier:** the portal can only select an entity by an attribute that is one of its target's configured `unique_ids` — for example `customer_number` or `contract_number` when the end customer types them. Anything else can never match, and the wait rejects it with 400.

:::tip
The full recipe, including the request and response contract, the failure cases, the `interactive` use case option, the rate limits and the Kafka variant, is on the [Interactive Registration](./interactive-registration.md) page.
:::

---

## Outbound Use Cases (epilot to ERP)

Outbound use cases push epilot events to your ERP via [Webhooks](/docs/integrations/webhooks). When a user performs a self-service action in a portal, journey, or epilot 360, an automation triggers a [Core Event](/docs/integrations/core-events) which is delivered to your middle layer webhook endpoint. Your middle layer then processes the event and calls the appropriate ERP API.
Expand Down
3 changes: 3 additions & 0 deletions scripts/update-monitoring-codes.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ A code's level is fixed. That is what makes alert rules scoped to \`_error_\` or
\`_warning_\` predictable — see [Alerting](./alerting.md#choosing-what-to-watch).
:::

The interactive lane codes (\`INTERACTIVE_*\` and \`CORRELATION_*\`) are explained in
context, with when each is raised, in [Interactive Registration](../interactive-registration.md#monitoring).

${sections.join('\n')}${familySection}`;
}

Expand Down
Loading