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

# Microsoft Teams

> Connect to your users' Microsoft Teams accounts.

export const IntegrationsCompatibility = ({workflows = true, actionkit = false, proxy = true, managedSync = false, authType = "Basic Auth", integrationName: integrationNameProp, integrationSlug: integrationSlugProp}) => {
  const FEATURE_REQUEST_ENDPOINT = "https://agosnlmllwykglihfhiw.supabase.co/functions/v1/handle-vote";
  const FEATURE_REQUEST_HEADERS = {
    "Content-Type": "application/json",
    "Authorization": `Bearer sb_publishable_DlIzCjWe8NiqjZnLziegbg_P-w5L9X4`,
    'apikey': `sb_pubishable_DlIzCjWe8NiqjZnLziegbg_P-w5L9X4`
  };
  const SESSION_VOTE_PREFIX = "paragon_compat_vote:";
  const PURPLE = "rgb(102, 69, 230)";
  const slugifyFeature = label => label.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
  const integrationKeyFromPathname = pathname => {
    if (!pathname) return "";
    const match = pathname.match(/\/(?:resources\/)?integrations\/([^/?#]+)/);
    return match ? decodeURIComponent(match[1]).replace(/\/$/, "") : "";
  };
  const voteStorageKey = (integrationKey, featureKey) => `${SESSION_VOTE_PREFIX}${integrationKey}:${featureKey}`;
  const readVoteFromStorage = (integrationKey, featureKey) => {
    if (typeof sessionStorage === "undefined") return false;
    try {
      return sessionStorage.getItem(voteStorageKey(integrationKey, featureKey)) === "1";
    } catch {
      return false;
    }
  };
  const writeVoteToStorage = (integrationKey, featureKey) => {
    try {
      sessionStorage.setItem(voteStorageKey(integrationKey, featureKey), "1");
    } catch {}
  };
  const readPageTitleFallback = () => {
    if (typeof document === "undefined") return "";
    const h1 = document.querySelector("article h1") || document.querySelector("main h1") || document.querySelector('[class*="title"] h1') || document.querySelector("h1");
    const text = h1?.textContent?.trim();
    return text || "";
  };
  const getRuntimeConfig = () => {
    if (typeof window === "undefined") return null;
    return window.__PARAGON_FEATURE_REQUEST__ ?? null;
  };
  const resolveEndpoint = () => {
    const rt = getRuntimeConfig();
    if (rt?.endpoint) return String(rt.endpoint).trim();
    return String(FEATURE_REQUEST_ENDPOINT || "").trim();
  };
  const resolveHeaders = () => {
    const rt = getRuntimeConfig();
    const base = {
      ...FEATURE_REQUEST_HEADERS,
      ...rt?.headers && typeof rt.headers === "object" ? rt.headers : {}
    };
    return base;
  };
  const isValidEmail = s => (/^[^\s@]+@[^\s@]+\.[^\s@]+$/).test(String(s).trim());
  const renderCompatCheckSvg = (sizePx = 18) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" fill={PURPLE} style={{
    width: `${sizePx}px`,
    height: `${sizePx}px`,
    flexShrink: 0,
    display: "block",
    verticalAlign: "middle",
    margin: "0 auto"
  }} aria-hidden>
      <path d="M320 576C178.6 576 64 461.4 64 320C64 178.6 178.6 64 320 64C461.4 64 576 178.6 576 320C576 461.4 461.4 576 320 576zM438 209.7C427.3 201.9 412.3 204.3 404.5 215L285.1 379.2L233 327.1C223.6 317.7 208.4 317.7 199.1 327.1C189.8 336.5 189.7 351.7 199.1 361L271.1 433C276.1 438 282.9 440.5 289.9 440C296.9 439.5 303.3 435.9 307.4 430.2L443.3 243.2C451.1 232.5 448.7 217.5 438 209.7z" />
    </svg>;
  const products = useMemo(() => [{
    label: "Managed Sync",
    value: managedSync
  }, {
    label: "ActionKit",
    value: actionkit
  }, {
    label: "Workflows",
    value: workflows
  }, {
    label: "Proxy API",
    value: proxy
  }, {
    label: "Auth Type",
    value: authType
  }], [actionkit, managedSync, workflows, proxy, authType]);
  const [mounted, setMounted] = useState(false);
  const [resolvedIntegrationKey, setResolvedIntegrationKey] = useState(() => integrationSlugProp?.trim() || "");
  const [resolvedDisplayName, setResolvedDisplayName] = useState(() => integrationNameProp?.trim() || "Integration");
  const [inlineFormOpen, setInlineFormOpen] = useState(false);
  const [formFeature, setFormFeature] = useState({
    featureLabel: "",
    featureKey: ""
  });
  const [email, setEmail] = useState("");
  const [description, setDescription] = useState("");
  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState("");
  const [voteClickError, setVoteClickError] = useState("");
  const [voteLogging, setVoteLogging] = useState(false);
  const [loggingFeatureKey, setLoggingFeatureKey] = useState("");
  const [, bumpVoteUi] = useState(0);
  const inlineCardRef = useRef(null);
  useEffect(() => {
    setMounted(true);
    const path = typeof window !== "undefined" ? window.location.pathname : "";
    const fromPath = integrationSlugProp?.trim() || integrationKeyFromPathname(path);
    setResolvedIntegrationKey(fromPath);
    const fromDom = readPageTitleFallback();
    const name = integrationNameProp?.trim() || fromDom || (fromPath ? fromPath.replace(/-/g, " ") : "Integration");
    setResolvedDisplayName(name);
  }, [integrationNameProp, integrationSlugProp]);
  const dismissInlineForm = useCallback(() => {
    setInlineFormOpen(false);
    setSubmitError("");
  }, []);
  const handleRequestClick = useCallback(async ({featureLabel, featureKey}) => {
    setVoteClickError("");
    const integrationKey = resolvedIntegrationKey || integrationKeyFromPathname(typeof window !== "undefined" ? window.location.pathname : "");
    if (!integrationKey) {
      setVoteClickError("Could not determine integration. Set the integrationSlug prop on this page.");
      return;
    }
    if (readVoteFromStorage(integrationKey, featureKey)) {
      return;
    }
    setLoggingFeatureKey(featureKey);
    setVoteLogging(true);
    const votedAt = new Date().toISOString();
    const ctaPayload = {
      integration_key: integrationKey,
      integration_name: resolvedDisplayName,
      feature_key: featureKey,
      feature_label: featureLabel,
      vote_phase: "cta_click",
      voted_at: votedAt
    };
    try {
      const endpoint = resolveEndpoint();
      if (endpoint) {
        const res = await fetch(endpoint, {
          method: "POST",
          headers: resolveHeaders(),
          body: JSON.stringify(ctaPayload)
        });
        if (!res.ok) {
          const text = await res.text().catch(() => "");
          throw new Error(text || `Request failed (${res.status})`);
        }
      } else if (typeof console !== "undefined" && console.warn) {
        console.warn("[IntegrationsCompatibility] FEATURE_REQUEST_ENDPOINT is empty; vote not sent. Set endpoint or window.__PARAGON_FEATURE_REQUEST__.");
      }
      writeVoteToStorage(integrationKey, featureKey);
      bumpVoteUi(v => v + 1);
      setFormFeature({
        featureLabel,
        featureKey
      });
      setEmail("");
      setDescription("");
      setSubmitError("");
      setInlineFormOpen(true);
    } catch (err) {
      setVoteClickError(err instanceof Error ? err.message : "Could not log your vote. Please try again.");
    } finally {
      setVoteLogging(false);
      setLoggingFeatureKey("");
    }
  }, [resolvedIntegrationKey, resolvedDisplayName]);
  useEffect(() => {
    if (!inlineFormOpen) return undefined;
    const id = window.setTimeout(() => {
      inlineCardRef.current?.scrollIntoView?.({
        behavior: "smooth",
        block: "nearest"
      });
    }, 80);
    return () => window.clearTimeout(id);
  }, [inlineFormOpen]);
  const submitEnrichment = useCallback(async () => {
    const trimmed = email.trim();
    if (!isValidEmail(trimmed)) {
      setSubmitError("Please enter a valid work email.");
      return;
    }
    const integrationKey = resolvedIntegrationKey || integrationKeyFromPathname(typeof window !== "undefined" ? window.location.pathname : "");
    if (!integrationKey) {
      setSubmitError("Could not determine integration. Set the integrationSlug prop on this page.");
      return;
    }
    const {featureKey, featureLabel} = formFeature;
    const endpoint = resolveEndpoint();
    const enrichedAt = new Date().toISOString();
    const payload = {
      integration_key: integrationKey,
      integration_name: resolvedDisplayName,
      feature_key: featureKey,
      feature_label: featureLabel,
      vote_phase: "enrichment",
      email: trimmed,
      description: description.trim() || undefined,
      voted_at: enrichedAt,
      enriched_at: enrichedAt
    };
    setSubmitting(true);
    setSubmitError("");
    try {
      if (endpoint) {
        const res = await fetch(endpoint, {
          method: "POST",
          headers: resolveHeaders(),
          body: JSON.stringify(payload)
        });
        if (!res.ok) {
          const text = await res.text().catch(() => "");
          throw new Error(text || `Request failed (${res.status})`);
        }
      } else if (typeof console !== "undefined" && console.warn) {
        console.warn("[IntegrationsCompatibility] FEATURE_REQUEST_ENDPOINT is empty; enrichment not sent. Set endpoint or window.__PARAGON_FEATURE_REQUEST__.");
      }
      setInlineFormOpen(false);
      setEmail("");
      setDescription("");
    } catch (err) {
      setSubmitError(err instanceof Error ? err.message : "Something went wrong. Please try again.");
    } finally {
      setSubmitting(false);
    }
  }, [email, description, formFeature, resolvedDisplayName, resolvedIntegrationKey]);
  const renderCompatProductCell = ({product, integrationKey, onRequestClick, alignWide, voteLogging, loggingFeatureKey}) => {
    const isAuthType = product.label === "Auth Type";
    const href = typeof product.value === "string" && !isAuthType ? product.value : null;
    const featureKey = slugifyFeature(product.label);
    const alreadyVoted = !isAuthType && integrationKey && !href && !product.value ? readVoteFromStorage(integrationKey, featureKey) : false;
    const unsupported = !isAuthType && !href && !product.value;
    const cellInner = isAuthType ? product.value : href ? <div style={{
      display: "inline-flex",
      alignItems: "center",
      gap: "6px"
    }}>
        {renderCompatCheckSvg()}
        <a href={href} className="compat-doc-link">
          Docs
          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17l9.2-9.2M17 17V7H7" /></svg>
        </a>
      </div> : product.value ? renderCompatCheckSvg() : alreadyVoted ? <span style={{
      fontSize: "12px",
      fontWeight: 500,
      color: "rgba(55, 55, 58, 0.85)"
    }} className="compat-requested-text">Requested</span> : <button type="button" className="compat-pill" disabled={voteLogging && loggingFeatureKey === featureKey} onClick={() => onRequestClick({
      featureLabel: product.label,
      featureKey
    })} style={{
      cursor: voteLogging && loggingFeatureKey === featureKey ? "not-allowed" : "pointer",
      opacity: voteLogging && loggingFeatureKey === featureKey ? 0.65 : 1
    }}>
        <svg className="compat-pill-plus" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{
      color: PURPLE
    }} aria-hidden>
          <line x1="12" y1="5" x2="12" y2="19"></line>
          <line x1="5" y1="12" x2="19" y2="12"></line>
        </svg>
        {voteLogging && loggingFeatureKey === featureKey ? "Submitting..." : "Request"}
      </button>;
    if (alignWide) {
      return <div style={{
        fontSize: "13px",
        padding: "8px 8px",
        minHeight: unsupported || alreadyVoted ? "40px" : undefined,
        flex: 1,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        textAlign: "center"
      }}>
          {cellInner}
        </div>;
    }
    return <span style={{
      fontSize: "13px",
      width: "80px",
      textAlign: "center",
      display: "inline-flex",
      justifyContent: "center"
    }}>
        {cellInner}
      </span>;
  };
  return <div style={{
    backgroundColor: "transparent",
    border: "0px solid rgba(225, 225, 229, 1)",
    borderRadius: "12px",
    padding: "0px 0px",
    overflow: "hidden"
  }}>
      <style>{`
        .compat-wide { display: flex; }
        .compat-narrow { display: none; }
        @media (max-width: 640px) {
          .compat-wide { display: none !important; }
          .compat-narrow { display: flex !important; }
        }
        
        .compat-doc-link {
          color: rgb(102, 69, 230);
          font-weight: 500;
          font-size: 13px;
          display: inline-flex;
          align-items: center;
          gap: 2px;
          text-decoration: none;
        }
        .compat-doc-link:hover {
          text-decoration: underline;
          text-underline-offset: 2px;
        }
        .dark .compat-doc-link {
          color: rgb(162, 140, 255);
        }

        .compat-pill {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          gap: 4px;
          box-sizing: border-box;
          min-height: 32px;
          padding: 6px 12px;
          border-radius: 999px;
          border: 1px solid rgba(0, 0, 0, 0.12);
          background-color: rgba(250, 250, 249, 1);
          font-size: 12px;
          font-weight: 500;
          color: rgba(35, 35, 38, 1);
          font-family: inherit;
          line-height: 1.2;
          width: auto;
          text-decoration: none;
          margin: 0;
          transition: box-shadow 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
        }
        .compat-pill:hover:not(:disabled) {
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        }
        
        a.compat-doc-link {
          text-decoration: underline !important;
          text-underline-offset: 2px !important;
          border-bottom: none !important;
          box-shadow: none !important;
          background: none !important;
        }
        a.compat-doc-link:hover {
          opacity: 0.8;
        }

        .dark .compat-pill {
          background-color: rgba(255, 255, 255, 0.05);
          border-color: rgba(255, 255, 255, 0.1);
          color: rgba(255, 255, 255, 0.85);
        }
        .dark .compat-pill:hover:not(:disabled) {
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
          background-color: rgba(255, 255, 255, 0.1);
        }
        .dark .compat-pill-plus {
          color: rgba(255, 255, 255, 0.85) !important;
        }
        .dark .compat-requested-text {
          color: rgba(255, 255, 255, 0.6) !important;
        }

        .compat-form-card {
          margin-top: 24px;
          width: 100%;
          box-sizing: border-box;
          background-color: #fff;
          border-radius: 16px;
          border: 1px solid rgba(0, 0, 0, 0.1);
          box-shadow: 0 8px 24px rgba(0,0,0,0.06);
          padding: 24px;
        }
        .dark .compat-form-card {
          background-color: #0f1114;
          border-color: rgba(255, 255, 255, 0.1);
          box-shadow: 0 8px 24px rgba(0,0,0,0.4);
        }

        .compat-form-close-btn {
          flex-shrink: 0;
          display: inline-flex;
          align-items: center;
          justify-content: center;
          width: 36px;
          height: 36px;
          margin: 0;
          padding: 0;
          border-radius: 8px;
          background: #fff;
          color: rgba(35, 35, 38, 0.75);
          font-size: 24px;
          line-height: 1;
          font-family: inherit;
          border: none;
        }
        .dark .compat-form-close-btn {
          background: rgba(255,255,255,0.05);
          color: rgba(255,255,255,0.7);
        }

        .compat-form-title {
          margin: 0 0 16px;
          font-size: 20px;
          font-weight: 700;
          color: #111;
        }
        .dark .compat-form-title {
          color: rgba(255, 255, 255, 0.95);
        }

        .compat-form-text {
          display: block;
          margin: 0 0 20px;
          font-size: 14px;
          color: rgba(55, 55, 58, 0.72);
          line-height: 1.5;
        }
        .dark .compat-form-text {
          color: rgba(255, 255, 255, 0.6);
        }

        .compat-form-label {
          display: flex;
          flex-direction: column;
          gap: 6px;
          font-size: 13px;
          font-weight: 500;
          color: #333;
        }
        .dark .compat-form-label {
          color: rgba(255, 255, 255, 0.85);
        }

        .compat-form-input-readonly {
          width: 100%;
          box-sizing: border-box;
          padding: 10px 12px;
          border-radius: 8px;
          border: 1px solid rgba(0, 0, 0, 0.08);
          background-color: rgba(250, 248, 245, 0.95);
          font-size: 14px;
          color: rgba(35, 35, 38, 0.95);
        }
        .dark .compat-form-input-readonly {
          background-color: rgba(255, 255, 255, 0.05);
          border-color: rgba(255, 255, 255, 0.1);
          color: rgba(255, 255, 255, 0.7);
        }

        .compat-form-input {
          width: 100%;
          box-sizing: border-box;
          padding: 10px 12px;
          border-radius: 8px;
          border: 1px solid rgba(0, 0, 0, 0.15);
          background-color: #fff;
          font-size: 14px;
          font-family: inherit;
          color: #111;
        }
        .dark .compat-form-input {
          background-color: rgba(255, 255, 255, 0.05);
          border-color: rgba(255, 255, 255, 0.15);
          color: rgba(255, 255, 255, 0.95);
        }

        .compat-form-submit {
          padding: 10px 16px;
          border-radius: 8px;
          border: 1px solid rgba(0, 0, 0, 0.15);
          background: #fff;
          font-size: 14px;
          font-weight: 700;
          font-family: inherit;
          color: #111;
        }
        .dark .compat-form-submit {
          background: rgba(255, 255, 255, 0.1);
          border-color: rgba(255, 255, 255, 0.2);
          color: rgba(255, 255, 255, 0.95);
        }
        .compat-form-tag {
          display: inline-flex;
          align-items: center;
          gap: 6px;
          padding: 6px 10px;
          border-radius: 8px;
          background-color: rgba(102, 69, 230, 0.12);
          border: 1px solid rgba(102, 69, 230, 0.35);
          color: rgb(102, 69, 230);
          font-size: 12px;
          font-weight: 600;
        }
        .dark .compat-form-tag {
          background-color: rgba(162, 140, 255, 0.15);
          border-color: rgba(162, 140, 255, 0.4);
          color: rgb(180, 160, 255);
        }
      `}</style>

      <div className="compat-wide" style={{
    flexDirection: "row",
    gap: "0px"
  }}>
        {products.map(f => <div key={f.label} style={{
    flex: 1,
    minWidth: 0,
    textAlign: "center",
    display: "flex",
    flexDirection: "column"
  }}>
            <div style={{
    fontSize: "13px",
    fontWeight: 500,
    padding: "6px 8px",
    borderBottom: "1px solid rgba(102, 69, 230, 0.15)",
    overflow: "hidden",
    textOverflow: "ellipsis",
    whiteSpace: "nowrap",
    textAlign: "center"
  }}>
              {f.label}
            </div>
            {renderCompatProductCell({
    product: f,
    integrationKey: mounted ? resolvedIntegrationKey : "",
    onRequestClick: handleRequestClick,
    alignWide: true,
    voteLogging,
    loggingFeatureKey
  })}
          </div>)}
      </div>

      <div className="compat-narrow" style={{
    flexDirection: "column",
    gap: "0px"
  }}>
        <div style={{
    display: "flex",
    justifyContent: "space-between",
    borderBottom: "1px solid rgba(102, 69, 230, 0.15)",
    padding: "6px 0"
  }}>
          <span style={{
    fontSize: "13px",
    fontWeight: 500
  }}>Product</span>
          <span style={{
    fontSize: "13px",
    fontWeight: 500,
    width: "80px",
    textAlign: "center"
  }}>Supported</span>
        </div>
        {products.map(f => <div key={f.label} style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "5px 0"
  }}>
            <span style={{
    fontSize: "13px"
  }}>{f.label}</span>
            {renderCompatProductCell({
    product: f,
    integrationKey: mounted ? resolvedIntegrationKey : "",
    onRequestClick: handleRequestClick,
    alignWide: false,
    voteLogging,
    loggingFeatureKey
  })}
          </div>)}
      </div>

      {voteClickError ? <p style={{
    margin: "16px 0 0",
    fontSize: "13px",
    color: "#b42318"
  }} role="alert">
          {voteClickError}
        </p> : null}

      {inlineFormOpen ? <div ref={inlineCardRef} role="region" aria-labelledby="compat-feature-request-title" className="compat-form-card">
          <div style={{
    display: "flex",
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
    gap: "12px",
    marginBottom: "16px"
  }}>
            <div className="compat-form-tag">
              Feature request
            </div>
            <button type="button" onClick={dismissInlineForm} disabled={submitting} aria-label="Close feature request form" className="compat-form-close-btn" style={{
    cursor: submitting ? "not-allowed" : "pointer"
  }}>
              <span aria-hidden>×</span>
            </button>
          </div>
          <h2 id="compat-feature-request-title" className="compat-form-title">
            Request support for {formFeature.featureLabel}
          </h2>
          <p className="compat-form-text">
            Your vote has been recorded. Add your email and an optional note and we'll notify you when support is added.
          </p>

          <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "16px"
  }}>
            <label className="compat-form-label">
              Integration
              <input type="text" readOnly value={resolvedDisplayName} className="compat-form-input-readonly" tabIndex={-1} />
            </label>
            <label className="compat-form-label">
              Feature
              <input type="text" readOnly value={formFeature.featureLabel} className="compat-form-input-readonly" tabIndex={-1} />
            </label>
            <label className="compat-form-label">
              Work email
              <input type="email" name="email" autoComplete="email" placeholder="you@company.com" value={email} onChange={e => setEmail(e.target.value)} className="compat-form-input" disabled={submitting} />
            </label>
            <label className="compat-form-label">
              Describe your use case (optional)
              <textarea placeholder="What would you build if this were supported?" value={description} onChange={e => setDescription(e.target.value)} rows={4} className="compat-form-input" style={{
    resize: "vertical",
    minHeight: "96px"
  }} disabled={submitting} />
            </label>
          </div>

          {submitError ? <p style={{
    margin: "14px 0 0",
    fontSize: "13px",
    color: "#b42318"
  }} role="alert">
              {submitError}
            </p> : null}

          <div style={{
    display: "flex",
    justifyContent: "flex-end",
    gap: "10px",
    marginTop: "22px",
    flexWrap: "wrap"
  }}>
            <button type="button" onClick={submitEnrichment} disabled={submitting} className="compat-form-submit" style={{
    cursor: submitting ? "not-allowed" : "pointer"
  }}>
              {submitting ? "Submitting…" : "Submit details"}
            </button>
          </div>
        </div> : null}
    </div>;
};

<IntegrationsCompatibility workflows={true} actionkit="/actionkit/integrations/microsoftTeams" proxy={true} managedSync="/managed-sync/integrations/microsoft-teams" authType="OAuth2" />

## Setup Guide

You can find your Microsoft Teams application credentials by visiting the [Microsoft Developer Portal for Teams](https://dev.teams.microsoft.com/).

You'll need the following information to set up your Microsoft Teams app with Paragon Connect:

* App ID
* Bot ID
* Bot Client Secret
* Scopes Requested

### Prerequisites

* A [Microsoft Azure](https://azure.microsoft.com/) account
* A bot registered in the Microsoft Developer Portal for Teams (*See* [*Creating a Microsoft Teams bot*](/resources/integrations/microsoft-teams#undefined) *for setup instructions*)

### Creating a Microsoft Teams bot

You will need to create a bot user in the [Microsoft Developer Portal for Teams](https://dev.teams.microsoft.com/) to build your integration.

1. Log in to the Developer Portal for Teams using the same Microsoft account that you use to log in to the Microsoft Azure Portal.

2. Navigate to **Tools** in the sidebar.

3. Select **Bot Management**.

4. Click **New Bot** and provide a name for your bot. *This is not the name that appears to your users*; we recommend appending "(Bot)" to the name for clarity.

Next, you will need to associate this new bot with your Teams application.

1. In the Developer Portal for Teams, navigate to **Apps** in the sidebar.

2. If you do not have a Teams app yet, click **New app** and provide a name. This name will be the one displayed in the Microsoft Teams store and to your users.

   1. In **Basic information**, you will need to minimally provide a short description and URLs for your application's website, Privacy Policy, and Terms of Use.

   2. Click **Save** below.

3. In your Teams app settings, navigate to **App features** (under the **Configure** section).

4. Select **Bot**.

5. Select the bot user you created above.

6. Check any applicable capabilities for your bot user.

7. Click **Save** below.

8. Optionally, upload an icon for your app in **Branding**.

<Frame>
  <img src="https://mintcdn.com/paragon/lKoo2WTuuveEy7P1/assets/adding-a-bot-to-your-microsoft-teams-app.png?fit=max&auto=format&n=lKoo2WTuuveEy7P1&q=85&s=9f526c0a5f3d015c60b5dd653a1c0f71" alt="" width="2458" height="1544" data-path="assets/adding-a-bot-to-your-microsoft-teams-app.png" />
</Frame>

#### Testing your Microsoft Teams bot

If your application is not yet published to the Microsoft Teams store, you can test your app by uploading your app's package directly into your Teams account.

1. In the [Developer Portal for Teams](https://dev.teams.microsoft.com/), navigate to your list of Apps.

2. Click the triple-dot menu in your app's row and click **Download app package**. Your app's package will be downloaded as a .zip file.

<Frame>
  <img src="https://mintcdn.com/paragon/fpLCYjVDKL_JwtxK/assets/downloading-your-microsoft-teams-app-package.png?fit=max&auto=format&n=fpLCYjVDKL_JwtxK&q=85&s=1c30eaf90bcf46d4824b841cdc2ac1bc" alt="" width="488" height="394" data-path="assets/downloading-your-microsoft-teams-app-package.png" />
</Frame>

3. Log in to Microsoft Teams with the account you would like to test your bot in.

4. Click **Apps** in the left sidebar (one of the last options):

<Frame>
  <img src="https://mintcdn.com/paragon/mM8ZO0IfeKOuG22m/assets/opening-the-apps-menu-in-microsoft-teams.png?fit=max&auto=format&n=mM8ZO0IfeKOuG22m&q=85&s=242e1325bfb059a2d8ca00cda677bc36" alt="" width="58" height="56" data-path="assets/opening-the-apps-menu-in-microsoft-teams.png" />
</Frame>

5. Click **Manage your apps** (at the bottom of the App categories sidebar).

6. Click **Upload an app** and **Upload your app to your org's app catalog**.

7. Select the .zip file with your app's package. After a successful upload, you will be navigated to the "Built for your org" page.

8. Select the name of your app in the list.

9. Click **Add** and **Add to a team**.

<Frame>
  <img src="https://mintcdn.com/paragon/aw0eYqgqzvO13GcW/assets/adding-your-microsoft-teams-app-to-a-team.png?fit=max&auto=format&n=aw0eYqgqzvO13GcW&q=85&s=e6181240f5d6fc2fa5699d4d7f3ed2ce" alt="" width="798" height="472" data-path="assets/adding-your-microsoft-teams-app-to-a-team.png" />
</Frame>

10. Select a team and channel to add your bot.

### Add the Redirect URL to your Microsoft Teams app

Paragon provides a redirect URL to send information to your app. To add the redirect URL to your Microsoft Teams app:

1. Copy the link under "**Redirect URL**" in your integration settings in Paragon. The Redirect URL is `https://passport.useparagon.com/oauth`

2. Log in to the [Microsoft Azure Portal](https://azure.microsoft.com/) using your Microsoft account.

3. Navigate to **All Services > App Registrations** and select the app registration *representing your bot, not your Microsoft Teams app*. You can check to make sure that the ID matches the one you selected for your associated bot: *In Developer Portal for Teams > \[Your App] > App Features:*

<Frame>
  <img src="https://mintcdn.com/paragon/fpLCYjVDKL_JwtxK/assets/finding-the-bot-app-id-in-the-developer-portal-for-teams.png?fit=max&auto=format&n=fpLCYjVDKL_JwtxK&q=85&s=016c9c8475c4c17ab05a9e628babda6b" alt="" width="490" height="194" data-path="assets/finding-the-bot-app-id-in-the-developer-portal-for-teams.png" />
</Frame>

4. Select **Authentication** from the sidebar.

5. Under **Platform configurations**, press the **"Add a platform"** button.

6. Select the **Web** platform.

7. Paste the Redirect URL from Step 1 under Redirect URIs.

8. Press the **Save** button at the top of the page.

### Generate a Bot Client Secret

Since Microsoft Teams does not automatically provide you with a Bot Client Secret for your application, we'll need to make one. To get your Bot Client Secret:

1. Navigate to **All Services > App Registrations** and select the app registration *representing your bot, not your Microsoft Teams app*. You can check to make sure that the ID matches the one you selected for your associated bot: *In Developer Portal for Teams > \[Your App] > App Features:* <img src="https://mintcdn.com/paragon/fpLCYjVDKL_JwtxK/assets/finding-the-bot-app-id-in-the-developer-portal-for-teams.png?fit=max&auto=format&n=fpLCYjVDKL_JwtxK&q=85&s=016c9c8475c4c17ab05a9e628babda6b" alt="" width="490" height="194" data-path="assets/finding-the-bot-app-id-in-the-developer-portal-for-teams.png" /> *In Azure Portal > App Registrations:* <img src="https://mintcdn.com/paragon/fdJ50ZEE8l2RCUKL/assets/verifying-the-bot-app-registration-id-in-azure-portal.png?fit=max&auto=format&n=fdJ50ZEE8l2RCUKL&q=85&s=b87b1c0d5e188cc007ebbdd996f25e41" alt="" width="1146" height="74" data-path="assets/verifying-the-bot-app-registration-id-in-azure-portal.png" />

2. Navigate to **Manage > Certificates & secrets** in the sidebar.

3. Under **Client Secrets**, click the **+ New client secret** button.

4. Name your client credentials and select an expiry that works best for your application. Press **Add** to create your credentials.

5. Copy the displayed Bot Client Secret under the **Value** column.

<Info>
  **Note:** You will need to periodically create new and update your Bot Client Secret as they expire for all Microsoft integrations.
</Info>

### Add your Microsoft Teams app to Paragon

1. Select **Microsoft Teams** from the **Integrations Catalog**.

2. Under **Integrations > Connected Integrations > Microsoft Teams > App Configuration > Configure**, fill out your credentials from the end of [Step 1](/resources/integrations/microsoft-teams#add-the-redirect-url-to-your-microsoft-teams-app) and [Step 2](/resources/integrations/microsoft-teams#generate-a-client-id-and-client-secret) in their respective sections:

* **App/Manifest ID:** Found in the [Microsoft Developer Portal for Teams](https://dev.teams.microsoft.com/), as the **App ID:**

  <Frame>
    <img src="https://mintcdn.com/paragon/fpLCYjVDKL_JwtxK/assets/finding-your-microsoft-teams-app-id-in-the-developer-portal.png?fit=max&auto=format&n=fpLCYjVDKL_JwtxK&q=85&s=de96d859653abd7b732a7054791a5e86" alt="" width="862" height="230" data-path="assets/finding-your-microsoft-teams-app-id-in-the-developer-portal.png" />
  </Frame>

* **Bot ID:** Found in the Microsoft Azure Portal, under App registrations > Application (client) ID. This should be the Client ID of the app registration *representing your bot, not your Microsoft Teams app.*

* **Bot Client Secret:** See [Generate a Bot Client Secret](/resources/integrations/microsoft-teams#generate-a-bot-client-secret).

* **Permissions:** Select the scopes you've requested for your application. For a list of recommended scopes, please view this integration within your Paragon dashboard. [View dashboard.](https://dashboard.useparagon.com)

Press the purple "**Save Changes**" button to save your credentials.

<Info>
  **Note:** You should only add the scopes you've requested in your application page to Paragon.
</Info>

<Frame>
  <img src="https://mintcdn.com/paragon/JtnB3_DeUOyB_qxZ/assets/Connecting%20your%20Microsoft%20Teams%20applications%20to%20Paragon%20Connect.png?fit=max&auto=format&n=JtnB3_DeUOyB_qxZ&q=85&s=1e2dba9d3f57874611c5e61247954a10" alt="" width="1832" height="978" data-path="assets/Connecting your Microsoft Teams applications to Paragon Connect.png" />
</Frame>

## Connecting to Microsoft Teams

Once your users have connected their Microsoft Teams account, you can use the Paragon SDK to access the Microsoft Teams API on behalf of connected users.

See the Microsoft Teams [REST API documentation](https://docs.microsoft.com/en-us/graph/api/resources/teams-api-overview?view=graph-rest-1.0) for their full API reference.

Any Microsoft Teams API endpoints can be accessed with the Paragon SDK as shown in this example.

```javascript theme={null}
// You can find your project ID in the Overview tab of any Integration

// Authenticate the user
paragon.authenticate(<ProjectId>, <UserToken>);

// List Channels
paragon.request("microsoftTeams", "channels", {
  method: "GET",
});


// Send message in a channel
paragon.request("microsoftTeams", "channels/<channel-id>/messages", {
  method: "POST",
  body: { "content": "Hello World!" }
});
```

## Building Microsoft Teams workflows

Once your Microsoft Teams account is connected, you can add steps to perform the following actions:

* Send Message in Channel
* Send Message in Chat
* Get User by Email
* List Messages in a Chat

You can also use the [Microsoft Teams Request](/workflows/requests#making-integration-requests) step to access any of Microsoft Teams's API endpoints without the authentication piece.

When creating messages in Microsoft Teams, you can reference data from previous steps by typing `{{` to invoke the variable menu.

<Frame>
  <img src="https://mintcdn.com/paragon/HSp5hB8tE4Z6e44m/assets/Sending%20messages%20to%20Microsoft%20Teams%20using%20Paragon%20Connect%20(1).png?fit=max&auto=format&n=HSp5hB8tE4Z6e44m&q=85&s=a831b0797b196624403bfcee0b1a7997" alt="" width="2178" height="1462" data-path="assets/Sending messages to Microsoft Teams using Paragon Connect (1).png" />
</Frame>

## Specifying bot tokens with Connect Proxy Requests

When using the [Proxy API](/apis/proxy) with Microsoft Teams, you can control whether requests are made using a **user token** or a **bot token** by including the following header:

```
x-paragon-use-ms-teams-token-type: bot
```

<Info>
  **Note:** The base URL for Microsoft Teams API requests differs depending on the token type being used:

  * **User token** → `https://graph.microsoft.com/v1.0`
  * **Bot token** → `https://smba.trafficmanager.net/`
</Info>

## Using Webhook Triggers

Webhook triggers can be used to run workflows based on events in your users' Microsoft Teams account. For example, you might want to trigger a workflow whenever new chats are created in Microsoft Teams to sync your users' Microsoft Teams chats to your application in real-time.

<Frame>
  <img src="https://mintcdn.com/paragon/7RZyQGncIlY8Xl4A/assets/Microsoft%20Teams%20triggers%20in%20Paragon%20Connect.png?fit=max&auto=format&n=7RZyQGncIlY8Xl4A&q=85&s=392fba8e7e744216bb187acbe7668feb" alt="" width="2196" height="1398" data-path="assets/Microsoft Teams triggers in Paragon Connect.png" />
</Frame>

You can find the full list of Webhook Triggers for Microsoft Teams below:

* **Channel Created**
* **Chat Created**
* **Chat Updated**
