# Webhooks

Webhooks deliver the results of a flow to an endpoint as they are processed. An inbound flow ends in a webhook output node, so the JSON it produces from your trading partner's EDI is pushed to you rather than something you have to poll for. For the shape of that JSON, see [JSON Format](/resources/docs/json-format).

## Webhook Kinds

When you create a webhook you choose its kind, and the kind is fixed for the life of the webhook:

- **Standard**: delivers to your own HTTPS endpoint. This is what you use in production.
- **Sandbox**: delivers to an in-app sink instead of an endpoint of your own. Use it to see exactly what a real delivery looks like while you are building and testing, before you have an endpoint ready.

You cannot switch a webhook between standard and sandbox after it is created. Create a new webhook if you need the other kind.

## Creating a Webhook

1. Go to **Webhooks** and click **Add Webhook**
2. Give the webhook a **Name**
3. Choose the **Kind** (Standard or Sandbox)
4. Fill in the remaining fields, which depend on the kind:
   - **URL** (standard only): the endpoint where deliveries are sent. Must start with `http://` or `https://`.
   - **Content Type**: the content type header sent with each delivery. Defaults to `application/json`.
   - **Secret Key** (standard only): used to sign each delivery so you can verify it came from Tediware. Leave it blank to have one generated for you.
   - **API Key** (standard only): sent to your endpoint as an `X-Api-Key` header so you can authenticate the request.
   - **Tags**: optional labels for organizing your webhooks.

## Standard Webhooks

A standard webhook sends each delivery as an HTTP request to your URL. Alongside the body, Tediware sends these headers:

- `Content-Type`: the content type you configured, `application/json` by default
- `X-Webhook-Signature`: the signature used to verify the delivery
- `X-Request-ID`: the delivery's idempotency key, stable across every retry of one delivery (see Retries below)
- `X-Webhook-Attempt`: which attempt of this delivery the request is, from 1 to at most 6
- `X-Api-Key`: the API key you configured, if any

### Verifying the Signature

Each delivery is signed so you can confirm it is genuine. The signature is an HMAC-SHA256 of the signed string `<timestamp>.<body>` using your webhook's secret key, and it is sent as the `v1` value in the `X-Webhook-Signature` header. To verify a delivery, recompute the HMAC on your side and compare it to `v1`. If they match, the delivery is authentic and unmodified.

### Retries

If your endpoint answers with a server error (500, 502, 503, or 504), 429, or a timeout, or the connection is refused, reset, or closed before a response, Tediware retries on a schedule: 6 attempts in total, spread over about 16 minutes, which covers a typical deploy window. A `Retry-After` header on a 429 or 503 can lengthen a wait, never shorten it. Every attempt of one delivery carries the same `X-Request-ID`, so treat it as an idempotency key rather than assuming a fresh header means a new event.

A 4xx other than 429, or any other 5xx, is not retried: a 4xx says the request itself is wrong. Because a 500 is retried, make your handler safe to run twice for one `X-Request-ID`. When a delivery stays failed, no further attempt is made on its own; for an inbound document, you can have Tediware redeliver it (see [EDI Transactions](/resources/docs/edi-transactions)).

## Sandbox Webhooks

A sandbox webhook delivers to an in-app sink so you can inspect a real delivery without standing up an endpoint. Open the webhook and click **View sink** to see its deliveries.

### The Sink Page

The sink page shows the webhook's sink URL and signing secret, followed by every delivery it has captured. Each delivery shows:

- When it was received and whether its signature verified
- Its kind: **Inbound** (EDI received from a partner and translated to JSON), **Outbound** (your JSON generated into EDI), or **Error**
- The request body and headers exactly as they were sent
- A signature breakdown: the signed string, the HMAC computed from your secret, and the `v1` value from the header, so you can see verification working step by step

The list refreshes on its own, so a delivery appears shortly after you run a flow.

### The Sandbox API Key

To fetch the result behind a delivery, a consumer needs an API key. On the sink page, click **Provision sandbox API key** to create one. The key is scoped to that sink: it can only read the results delivered there and their artifacts, so it is safe to copy and experiment with. You can revoke it at any time from **API Keys**.

### Fetching Results and Artifacts

Each delivery body carries a `resultId`. The sink page gives you a ready-made command to fetch the result and each of its artifacts, and an **Execute** button that runs the call in the browser using your sandbox key. Every artifact has a usage: the input artifact is the document the flow received, and the output artifact is always the document that was delivered. When you receive EDI from a partner, the input is their EDI and the output is the delivered JSON; if the transaction setting has a mapping, the result also carries a translation artifact, the parsed JSON the mapping transformed. When you send EDI to a partner, the input is your JSON and the output is the generated EDI. If an inbound mapping fails on a document, delivery still happens, and the result's detail carries `mappingFailed: true` and a `mappingError` describing what went wrong; the output is then either the untransformed translation (the transformation failed to run) or mapped output that did not validate against the canonical shape, per `mappingError.kind` (see Mappings).

### Sandbox Flows

A flow that delivers to a sandbox webhook is always **Paused** and cannot be processed automatically. Run it yourself with **Process** while you are testing. Sandbox documents are test documents: each result carries `usageIndicator: "T"`, so code that skips test documents skips these too. Its captured deliveries appear inline on the flow page as well as on the sink page.

## Using Webhooks in Flows

Inbound flows deliver their results to a webhook. When you configure a partner for inbound processing, you assign the inbound webhook that the built flow delivers to. See **Flows** for how flows are built and run.

A partner has three webhook slots: inbound, outbound and error. Give the outbound and error slots different webhooks. Both deliveries send the same body and headers, with no field naming the event, so one webhook serving both slots leaves your endpoint unable to tell a delivered document from a failed one without fetching the result.

The `resultId` in a delivery points at a different result for each slot. An inbound delivery points at the result that holds the delivered JSON. An outbound delivery points at the result of the step that sent the document to your partner, the SFTP upload or AS2 send, whose output artifact is the generated EDI. An error delivery points at the failed result of the step that failed, which carries the error message and, for a validation failure, each violation. Each webhook signs with its own secret key. Give each webhook its own URL (a distinct path or query string is enough), so your endpoint knows which secret to verify with and which event it received.
