> ## Documentation Index
> Fetch the complete documentation index at: https://docs.junojourney.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Send an HTTP request to your own system when an automation fires — for example, when a learner completes a course or passes a quiz.

export const RelatedPages = ({pages = []}) => {
  if (pages.length === 0) return null;
  return <>
      <br />
      <strong>Related articles</strong>
      <CardGroup cols={2}>
        {pages.map(page => <Card title={page.title} href={page.href} key={page.href} />)}
      </CardGroup>
    </>;
};

export const RoleBadge = ({roles = []}) => {
  const colorMap = {
    Admin: "yellow",
    Manager: "blue",
    Learner: "green",
    "Co-editor": "purple"
  };
  return <>
      {roles.map(role => <span key={role}><Badge color={colorMap[role] || "gray"} size="sm" shape="pill">{role}</Badge>{" "}</span>)}
    </>;
};

<RoleBadge roles={["Admin"]} />

Use the **Send an HTTP request (Webhook)** automation action to notify an outside system — an HRIS, CRM, or middleware like Zapier — when something happens in Juno. Juno sends a `POST` request with details about the learning and the learner.

<Note>
  Webhooks push events **out** of Juno. To pull data from Juno on your own schedule, use the [Juno API](https://developers.junojourney.com).
</Note>

***

## Set up a webhook

<Steps>
  <Step title="Open automations">
    Open **Automation** on a learning item, or go to **Admin → Automations**.
  </Step>

  <Step title="Choose the action">
    Create an automation, pick a trigger, and select **Send an HTTP request (Webhook)** as the action.
  </Step>

  <Step title="Fill in the request">
    * **Method** — `POST` is the only option.
    * **Url** — your endpoint.
    * **Additional data (JSON)** — optional. Any extra values you want included, in JSON format.
    * **Authorization** — required. Enter your token only; Juno sends it as `Authorization: Bearer <token>`.
  </Step>

  <Step title="Test and save">
    Select **Trigger the HTTP request** to send a test request, then save the automation.
  </Step>
</Steps>

The test request uses **your own** user details and the current time as the start and completion dates.

***

## Triggers

The action is available with these triggers, depending on the content type:

| Trigger                                                                                                  | Content                      |
| -------------------------------------------------------------------------------------------------------- | ---------------------------- |
| **Completed their training** / **Has not completed their training** / **Has not started their training** | Courses, Journeys, and SCORM |
| **Completed a training step** / **Not completed a training step**                                        | Journeys                     |
| **Passed the quiz**                                                                                      | Quizzes                      |
| **Session ends** / **After every session ends** / **Last learner's session in event has ended**          | Events                       |

Juno sends one request **per learner** the automation matches. Dry runs don't send requests.

***

## Request format

Every request has the header `Content-Type: application/json` and a JSON body with four keys:

* `trainingData` — `id`, `title`, `description`, `type` (for example `course` or `journey`), `photoUrl`, and the learner's `startDate` and `completedDate` (`YYYY-MM-DD HH:mm:ss`). Step triggers also include `stepNumber` and `stepTitle`.
* `userData` — `_id`, `primaryEmail`, `firstName`, `lastName`, `fullName`, `department`, `location`, `jobTitle`, `role`, and `customAttributes`.
* `automationTrigger` — the trigger ID, such as `training-completed` or `quiz-passed`.
* `additionalData` — what you entered in **Additional data (JSON)**.

```json theme={null}
{
  "trainingData": {
    "id": "65f1c0a2e4b0a1b2c3d4e5f6",
    "title": "Security Awareness 2026",
    "description": "Annual security training",
    "type": "course",
    "photoUrl": "https://…/cover.png",
    "startDate": "2026-09-01 09:12:44",
    "completedDate": "2026-09-03 16:40:02"
  },
  "userData": {
    "_id": "64a7…",
    "primaryEmail": "dana@example.com",
    "firstName": "Dana",
    "lastName": "Levi",
    "fullName": "Dana Levi",
    "department": "Sales",
    "location": "London",
    "jobTitle": "Account Executive",
    "role": 1,
    "customAttributes": { "region": "EMEA" }
  },
  "automationTrigger": "training-completed",
  "additionalData": "{\"source\":\"juno\"}"
}
```

<Note>
  On automated runs, `additionalData` arrives as the text you entered — parse it as JSON on your side. Values that don't apply (for example `completedDate` before completion) are left out.
</Note>

***

## Delivery and retries

* Each attempt times out after **30 seconds**.
* Juno makes up to **3 attempts**, waiting 5–15 seconds between them.
* A **2xx** response counts as success. Anything else — after the last attempt — is marked failed, with the status code as the error.
* Juno follows up to **3 redirects**. After that, the request fails with `TOO_MANY_REDIRECTS`.

### Check results

In the automation's **Automation Logs**, each webhook entry shows a green check when it succeeded, or an error icon — **Failed to reach 3rd party** with the error code — when it failed. Select **Resend the HTTP request for this user** to retry. A resend uses the automation's current URL and token, so you can fix the settings first. Response bodies aren't stored.

***

## Security

<Warning>
  Juno blocks requests to private and internal addresses. Saving is rejected, and sends fail with `BLOCKED_URL`, when the URL points to `localhost`, loopback (`127.x`, `::1`), private ranges (`10.x`, `172.16–31.x`, `192.168.x`, `fc00::`, `fd…`), link-local and cloud metadata addresses (`169.254.x`, `fe80::`, `metadata.google.internal`), hostnames ending in `.local`, `.localhost`, `.internal`, `.private`, or `.corp`, or a hostname that resolves to any of these. Every redirect is checked the same way.
</Warning>

<Warning>
  Use an `https://` URL. Juno accepts `http://` too, and adds `https://` when you leave the scheme out — but an `http://` endpoint sends your token unencrypted.
</Warning>

Your token is stored encrypted and masked in the form. Juno only sends it to the exact origin you configured — never to a redirect on a different host, port, or scheme.

<RelatedPages
  pages={[
{ href: "/admin/automations", title: "Automations" },
{ href: "/integrations/api-overview", title: "API Overview" },
]}
/>
