# Webhooks

Webhooks are the recommended way to receive processing outcomes when your system can host a public HTTPS endpoint. Tediware calls your endpoint the moment a document is processed, delivered, or hits an error, so you get a signed, event-driven push with nothing to schedule. Each delivery includes a `resultId` that your system uses to fetch the full result and download the translated data. If your platform cannot accept inbound HTTP requests, use Polling instead: the same outcomes are available from a feed you pull on your own schedule. See the Overview page for how the two approaches compare and how they fit into the inbound and outbound data flows.

## The Webhook Flow

These two flows show where the webhook fits. Tediware notifies your system the moment a document is processed (inbound) or delivered (outbound), and your system then fetches the full data through the Results API.

[INBOUND DIAGRAM]

[OUTBOUND DIAGRAM]

## Webhook Types

Each partner can have up to three webhooks configured:

- **Inbound Webhook** -- fires when inbound EDI from this partner has been processed and converted to JSON. This is typically the trigger for your system to fetch the translated data -- use the `resultId` from the payload to call `GET /platform/results/:id`, then download the output artifact. Required for inbound flows.
- **Outbound Webhook** -- fires when outbound EDI has been successfully generated and delivered to the partner's connection. Use the `resultId` to fetch the result if you need the generated EDI content or control numbers. Optional.
- **Error Webhook** -- fires when a processing error occurs in any of this partner's flows (inbound or outbound). The result's `errorMessage` field contains details about what went wrong. Optional.

Webhooks are assigned on the partner detail page under the **Configuration** section.

## Configuration

Webhooks are created on the **Webhooks** page and then assigned to partners. Each webhook has the following fields:

- **Name** -- a label for your reference
- **URL** -- the HTTP endpoint that will receive POST requests
- **Content Type** -- the `Content-Type` header sent with requests (defaults to `application/json`)
- **Secret Key** -- used to sign payloads so you can verify they came from Tediware. Auto-generated when the webhook is created. Displayed in full only at creation time -- store it securely.
- **API Key** -- an optional key sent in the `X-Api-Key` header. Use this if your receiving endpoint requires its own authentication.

## Payload Format

Every webhook delivery sends a JSON POST request with the following body:

```json
{
  "timestamp": "2026-03-30T14:22:33Z",
  "output": {
    "resultId": "4c2021f8-8310-4628-80f9-b578d4d68e11",
    "traceGuid": "5ab72145-4a4b-40f6-95de-e2579f163f79"
  }
}
```

- `timestamp` -- when the webhook was dispatched (ISO 8601 UTC)
- `output.resultId` -- the ID of the processing result. Use this with `GET /platform/results/:id` to retrieve the full result including artifacts.
- `output.traceGuid` -- the trace identifier linking all results from the same processing run

The payload is intentionally minimal. Tediware sends only identifiers, never the document content itself. Your system uses these identifiers to pull the full data through the Results API at its own pace. This keeps webhook payloads small, avoids transmitting sensitive business data over the webhook channel, and lets your system process results asynchronously.

## HTTP Headers

Each webhook request includes the following headers:

- `Content-Type` -- the content type configured on the webhook (default `application/json`)
- `X-Webhook-Signature` -- an HMAC-SHA256 signature for verifying the payload
- `X-Request-ID` -- a unique identifier for this delivery attempt
- `X-Api-Key` -- the API key configured on the webhook (only included if set)

## Verifying Signatures

Every webhook delivery is signed using your webhook's secret key. Verifying the signature confirms that the request came from Tediware and that the payload has not been tampered with.

### Signature Format

The `X-Webhook-Signature` header contains a timestamp and a signature separated by a comma:

```
t=1743350553,v1=5d41402abc4b2a76b9719d911017c592aeca6fba3b4e2f8c1d2e3f4a5b6c7d8e
```

- `t` -- the Unix timestamp when the signature was generated
- `v1` -- the HMAC-SHA256 hex digest

### Verification Steps

1. Extract the `t` and `v1` values from the `X-Webhook-Signature` header
2. Construct the signed payload string by joining the timestamp and the raw request body with a period: `{t}.{request_body}`
3. Compute the HMAC-SHA256 of the signed payload string using your webhook's secret key
4. Compare the computed hex digest with the `v1` value. If they match, the signature is valid.
5. Optionally, check that the timestamp is recent (e.g., within the last 5 minutes) to guard against replay attacks.

### Example (Node.js)

```javascript
const crypto = require("crypto");

function verifyWebhookSignature(rawBody, signatureHeader, secretKey) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("=", 2))
  );

  const signedPayload = `${parts.t}.${rawBody}`;
  const expected = crypto
    .createHmac("sha256", secretKey)
    .update(signedPayload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(parts.v1, "hex")
  );
}
```

### Example (Python)

```python
import hmac
import hashlib

def verify_webhook_signature(raw_body, signature_header, secret_key):
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    signed_payload = f"{parts['t']}.{raw_body}"
    expected = hmac.new(
        secret_key.encode(), signed_payload.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
```

## Retry Behavior

Tediware expects your webhook endpoint to respond within 10 seconds and return a 2xx status code. If the request times out or a network error occurs, Tediware retries up to 2 additional times (3 total attempts) with progressive delays:

- First retry: after 1 second
- Second retry: after 2 seconds

Non-2xx HTTP responses (such as 400 or 500) are treated as failures but are not retried. Only timeouts and connection errors trigger retries.

## Delivery History

Tediware records every webhook delivery attempt, including the payload sent, the response received, and any errors. These records are available for 30 days and are then automatically cleaned up. See the Data Retention documentation for details on all retention periods.

## Troubleshooting

- **Webhook not firing** -- Verify that the partner's flow is set to **Active** and that the webhook is assigned to the correct slot (inbound, outbound, or error) on the partner configuration.
- **Signature verification failing** -- Ensure you are using the raw request body (not a parsed and re-serialized version) when constructing the signed payload string. The secret key must match exactly what was provided at webhook creation.
- **Delivery timeouts** -- Your endpoint must respond within 10 seconds. If processing takes longer, accept the webhook immediately and handle the work asynchronously.
- **Repeated failures** -- Check the webhook delivery history in the application for status codes and error messages. Common issues include expired TLS certificates, firewall rules blocking Tediware's IP, or the endpoint being temporarily unavailable.
