Skip to main content
While Elements handle the UI and user interactions, you’ll need to monitor loan status changes by polling the GraphQL API. Polling is the recommended approach to track loan progress, stage changes, task completions, and other updates.

Why polling?

When borrowers interact with the Borrower Dashboard Element, the loan progresses through various stages and tasks are completed. To keep your application in sync with these changes, you need to poll the GraphQL API to detect:
  • Loan stage transitions (e.g., “Underwriting” → “Conditionally Approved” → “Clear to Close”)
  • New task assignments
  • Task completions
  • Document uploads
  • Disclosure status changes
  • Order-out status updates (appraisal, title, etc.)
What to monitorRecommended intervalDetection method
Loan stage15-30 minutesCompare currentStage
Tasks15-30 minutesTrack task IDs and status
Documents30-60 minutesTrack uploadedAt timestamps
Order-outs (appraisal/title)2-4 hoursCheck orderStatus fields
Disclosure status30-60 minutesCheck signed fields

Basic polling implementation

Here’s a simple example that polls for loan stage changes:
async function pollLoanStage(loanId: string) {
  const query = `
    query GetLoanStage($id: ID!) {
      loan(id: $id) {
        id
        currentStage
        stages {
          name
          timestamp
        }
      }
    }
  `;

  const response = await fetch("https://pylon.mortgage/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({ query, variables: { id: loanId } }),
  });

  const { data } = await response.json();
  return data.loan;
}

let previousStage: string | null = null;

// Poll every 30 minutes
const intervalId = setInterval(async () => {
  const loan = await pollLoanStage(loanId);
  
  if (previousStage !== null && previousStage !== loan.currentStage) {
    console.log(`Stage changed: ${previousStage}${loan.currentStage}`);
    // Notify user, update UI, etc.
  }
  
  previousStage = loan.currentStage;
}, 30 * 60 * 1000);

Monitoring tasks

Poll for new tasks and task status changes:
const previousTaskIds = new Set<string>();

async function pollTasks(loanId: string) {
  const query = `
    query GetTasks($loanId: ID!) {
      loan(id: $loanId) {
        borrowers(first: 10) {
          edges {
            node {
              borrowerTasks {
                id
                title
                status
                type
                completedAt
              }
            }
          }
        }
        loanOfficerDocumentTasks {
          id
          title
          status
          type
        }
      }
    }
  `;

  const response = await fetch("https://pylon.mortgage/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({ query, variables: { loanId } }),
  });

  const { data } = await response.json();
  const borrowerTasks = data.loan.borrowers.edges.flatMap(
    (edge: any) => edge.node.borrowerTasks
  );
  const allTasks = [...borrowerTasks, ...data.loan.loanOfficerDocumentTasks];
  
  const currentTaskIds = new Set(allTasks.map((t: any) => t.id));
  const newTasks = allTasks.filter(
    (task: any) => !previousTaskIds.has(task.id)
  );

  if (newTasks.length > 0) {
    console.log(`New tasks: ${newTasks.length}`);
    // Notify borrower, update UI, etc.
  }

  previousTaskIds.clear();
  currentTaskIds.forEach(id => previousTaskIds.add(id));
}

Monitoring order-outs

Track appraisal and title order status:
async function pollOrderOuts(loanId: string) {
  const query = `
    query GetOrderOuts($id: ID!) {
      loan(id: $id) {
        id
        appraisal {
          orderStatus
          scheduledAt
          inspectionCompletedAt
          receivedAt
        }
        title {
          # Add title status fields as needed
        }
      }
    }
  `;

  const response = await fetch("https://pylon.mortgage/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({ query, variables: { id: loanId } }),
  });

  const { data } = await response.json();
  return data.loan;
}

// Poll every 2-4 hours for order-outs
setInterval(async () => {
  const loan = await pollOrderOuts(loanId);
  
  if (loan.appraisal?.scheduledAt) {
    // Notify borrower about scheduled inspection
  }
  
  if (loan.appraisal?.orderStatus === "COMPLETED") {
    // Appraisal is complete
  }
}, 2 * 60 * 60 * 1000); // 2 hours

Complete polling guide

For comprehensive polling patterns, implementation examples, and best practices, see the Tracking loan updates guide. That guide covers:
  • Focused queries for efficient polling
  • State management and change detection
  • Error handling and retry logic
  • Polling multiple loans
  • Monitoring different entity types (stages, tasks, documents, order-outs, etc.)

Best practices

  1. Use focused queries - Only request the fields you need to detect changes
  2. Store previous state - Cache values you’re monitoring to detect changes
  3. Handle errors gracefully - Implement retry logic for transient failures
  4. Adjust intervals by entity - Poll stages/tasks more frequently than order-outs
  5. Stop polling when done - Clear intervals when loans are closed or no longer need monitoring

Next steps