🎉 Pipedream raises $20M Series A 🎉
Read the blog post and TC Techcrunch article.
← Google Calendar + Ahrefs integrations

Get Backlinks with Ahrefs APIon New or Updated Event (Instant) from Google Calendar API

Pipedream makes it easy to connect APIs for Ahrefs, Google Calendar and 700+ other apps remarkably fast.

Trigger workflow on
New or Updated Event (Instant) from the Google Calendar API
Next, do this
Get Backlinks with the Ahrefs API
No credit card required
Trusted by 250,000+ developers from startups to Fortune 500 companies:
Trusted by 250,000+ developers from startups to Fortune 500 companies

Developers Pipedream

Getting Started

This integration creates a workflow with a Google Calendar trigger and Ahrefs action. When you configure and deploy the workflow, it will run on Pipedream's servers 24x7 for free.

  1. Select this integration
  2. Configure the New or Updated Event (Instant) trigger
    1. Connect your Google Calendar account
    2. Select one or more Calendars
    3. Optional- Configure New events only?
    4. Configure Push notification renewal schedule
  3. Configure the Get Backlinks action
    1. Connect your Ahrefs account
    2. Configure target
    3. Optional- Select a mode
    4. Optional- Configure limit
  4. Deploy the workflow
  5. Send a test event to validate your setup
  6. Turn on the trigger

Details

This integration uses pre-built, open source components from Pipedream's GitHub repo. These components are developed by Pipedream and the community, and verified and maintained by Pipedream.

To contribute an update to an existing component or create a new component, create a PR on GitHub. If you're new to Pipedream component development, you can start with quickstarts for trigger span and action development, and then review the component API reference.

Trigger

Description:Emit new calendar events when an event is created or updated (does not emit cancelled events)
Version:0.1.1
Key:google_calendar-new-or-updated-event-instant

Trigger Code

import { v4 as uuid } from "uuid";
import googleCalendar from "../../google_calendar.app.mjs";

export default {
  key: "google_calendar-new-or-updated-event-instant",
  type: "source",
  name: "New or Updated Event (Instant)",
  description:
    "Emit new calendar events when an event is created or updated (does not emit cancelled events)",
  version: "0.1.1",
  dedupe: "unique",
  props: {
    googleCalendar,
    db: "$.service.db",
    calendarIds: {
      type: "string[]",
      label: "Calendars",
      description: "Select one or more calendars to watch",
      propDefinition: [
        googleCalendar,
        "calendarId",
      ],
    },
    newOnly: {
      label: "New events only?",
      type: "boolean",
      description: "Emit new events only, and not updates to existing events",
      optional: true,
      default: false,
    },
    http: "$.interface.http",
    timer: {
      label: "Push notification renewal schedule",
      description:
        "The Google Calendar API requires occasional renewal of push notification subscriptions. **This runs in the background, so you should not need to modify this schedule**.",
      type: "$.interface.timer",
      static: {
        intervalSeconds: 60 * 60 * 23,
      },
    },
  },
  hooks: {
    async activate() {
      await this.makeWatchRequest();
    },
    async deactivate() {
      await this.stopWatchRequest();
    },
  },
  methods: {
    setNextSyncToken(calendarId, nextSyncToken) {
      this.db.set(`${calendarId}.nextSyncToken`, nextSyncToken);
    },
    getNextSyncToken(calendarId) {
      return this.db.get(`${calendarId}.nextSyncToken`);
    },
    setChannelId(calendarId, channelId) {
      this.db.set(`${calendarId}.channelId`, channelId);
    },
    getChannelId(calendarId) {
      return this.db.get(`${calendarId}.channelId`);
    },
    setResourceId(calendarId, resourceId) {
      this.db.set(`${calendarId}.resourceId`, resourceId);
    },
    getResourceId(calendarId) {
      return this.db.get(`${calendarId}.resourceId`);
    },
    setExpiration(calendarId, expiration) {
      this.db.set(`${calendarId}.expiration`, expiration);
    },
    getExpiration(calendarId) {
      return this.db.get(`${calendarId}.expiration`);
    },
    /**
     * A utility method to compute whether the provided event is newly created
     * or not. Since the Google Calendar API does not provide a specific way to
     * determine this, this method estimates the result based on the `created`
     * and `updated` timestamps: if they are more than 2 seconds apart, then we
     * assume that the event is not new.
     *
     * @param {Object} event - The calendar event being processed
     * @returns {Boolean} True if the input event is a newly created event, or
     * false otherwise
     */
    _isNewEvent(event) {
      const {
        created,
        updated,
      } = event;
      const createdTimestampMilliseconds = Date.parse(created);
      const updatedTimestampMilliseconds = Date.parse(updated);
      const diffMilliseconds = Math.abs(
        updatedTimestampMilliseconds - createdTimestampMilliseconds,
      );
      const maxDiffMilliseconds = 2000;
      return diffMilliseconds <= maxDiffMilliseconds;
    },
    /**
     * A utility method to compute whether the provided event is relevant to the
     * event source (and as a consequence must be processed) or not.
     *
     * @param {Object} event - The calendar event being processed
     * @returns {Boolean} True if the input event must be processed, or false
     * otherwise (i.e. if the event must be skipped)
     */
    isEventRelevant(event) {
      return !this.newOnly || this._isNewEvent(event);
    },
    generateMeta(event) {
      const {
        id,
        summary,
        updated: tsString,
      } = event;
      const ts = Date.parse(tsString);
      return {
        id: `${id}-${ts}`,
        summary,
        ts,
      };
    },
    async makeWatchRequest() {
      // Make watch request for this HTTP endpoint
      for (const calendarId of this.calendarIds) {
        const watchResp =
          await this.googleCalendar.watchEvents({
            calendarId,
            requestBody: {
              id: uuid(),
              type: "web_hook",
              address: this.http.endpoint,
            },
          });

        // Initial full sync. Get next sync token
        const nextSyncToken = await this.googleCalendar.fullSync(calendarId);

        this.setNextSyncToken(calendarId, nextSyncToken);
        this.setChannelId(calendarId, watchResp.id);
        this.setResourceId(calendarId, watchResp.resourceId);
        this.setExpiration(calendarId, watchResp.expiration);
      }
    },
    async stopWatchRequest() {
      for (const calendarId of this.calendarIds) {
        const id = this.getChannelId(calendarId);
        const resourceId = this.getResourceId(calendarId);
        if (id && resourceId) {
          const { status } =
            await this.googleCalendar.stopChannel({
              returnOnlyData: false,
              requestBody: {
                id,
                resourceId,
              },
            });
          if (status === 204) {
            console.log("webhook deactivated");
            this.setNextSyncToken(calendarId, null);
            this.setChannelId(calendarId, null);
            this.setResourceId(calendarId, null);
            this.setExpiration(calendarId, null);
          } else {
            console.log("There was a problem deactivating the webhook");
          }
        }
      }
    },
  },
  async run(event) {
    // Verify channel ID
    const channelIds = [];
    for (const calendarId of this.calendarIds) {
      const channelId = this.getChannelId(calendarId);
      channelIds.push(channelId);
    }
    const incomingChannelId = event?.headers?.["x-goog-channel-id"];
    if (!channelIds.includes(incomingChannelId)) {
      console.log(
        `Unexpected channel ID ${incomingChannelId}. This likely means there are multiple, older subscriptions active.`,
      );
      return;
    }

    // Check that resource state === exists
    const state = event?.headers?.["x-goog-resource-state"];
    switch (state) {
    case "exists":
      // there's something to emit, so keep going
      break;
    case "not_exists":
      console.log("Resource does not exist. Exiting.");
      return;
    case "sync":
      console.log("New channel created");
      return;
    default:
      console.log(`Unknown state: ${state}`);
      return;
    }

    // Fetch and emit events
    for (const calendarId of this.calendarIds) {
      const syncToken = this.getNextSyncToken(calendarId);
      let nextSyncToken = null;
      let nextPageToken = null;
      while (!nextSyncToken) {
        const {
          data: syncData = {},
          status: syncStatus,
        } = await this.googleCalendar.listEvents({
          returnOnlyData: false,
          calendarId,
          syncToken,
          pageToken: nextPageToken,
        });
        if (syncStatus === 410) {
          console.log("Sync token invalid, resyncing");
          nextSyncToken = await this.googleCalendar.fullSync(this.calendarId);
          break;
        }
        nextPageToken = syncData.nextPageToken;
        nextSyncToken = syncData.nextSyncToken;

        const { items: events = [] } = syncData;
        events
          .filter(this.isEventRelevant, this)
          .forEach((event) => {
            const { status } = event;
            if (status === "cancelled") {
              console.log("Event cancelled. Exiting.");
              return;
            }
            const meta = this.generateMeta(event);
            this.$emit(event, meta);
          });
      }

      this.setNextSyncToken(calendarId, nextSyncToken);
    }
  },
};

Trigger Configuration

This component may be configured based on the props defined in the component code. Pipedream automatically prompts for input values in the UI and CLI.
LabelPropTypeDescription
Google CalendargoogleCalendarappThis component uses the Google Calendar app.
N/Adb$.service.dbThis component uses $.service.db to maintain state between component invocations.
CalendarscalendarIdsstring[]Select a value from the drop down menu.
New events only?newOnlyboolean

Emit new events only, and not updates to existing events

N/Ahttp$.interface.httpThis component uses $.interface.http to generate a unique URL when the component is first instantiated. Each request to the URL will trigger the run() method of the component.
Push notification renewal scheduletimer$.interface.timer

The Google Calendar API requires occasional renewal of push notification subscriptions. This runs in the background, so you should not need to modify this schedule.

Trigger Authentication

Google Calendar uses OAuth authentication. When you connect your Google Calendar account, Pipedream will open a popup window where you can sign into Google Calendar and grant Pipedream permission to connect to your account. Pipedream securely stores and automatically refreshes the OAuth tokens so you can easily authenticate any Google Calendar API.

Pipedream requests the following authorization scopes when you connect your account:

emailprofilehttps://www.googleapis.com/auth/calendar.eventshttps://www.googleapis.com/auth/calendar.readonlyhttps://www.googleapis.com/auth/calendar.settings.readonly

About Google Calendar

Scheduling calendar service developed by Google

Action

Description:Get the backlinks for a domain or URL with details for the referring pages (e.g., anchor and page title).
Version:0.0.8
Key:ahrefs-get-backlinks

Action Code

const ahrefs = require('../../ahrefs.app.js')
const axios = require('axios')

module.exports = {
  name: 'Get Backlinks',
  key: "ahrefs-get-backlinks",
  description: "Get the backlinks for a domain or URL with details for the referring pages (e.g., anchor and page title).",
  version: '0.0.8',
  type: "action",
  props: {
    ahrefs,
    target: { propDefinition: [ahrefs, "target"] },
    mode: { propDefinition: [ahrefs, "mode"] },
    limit: { propDefinition: [ahrefs, "limit"] },
  },
  async run() {
    return (await axios({
      url: `https://apiv2.ahrefs.com`,
      params: {
        token: this.ahrefs.$auth.oauth_access_token,
        from: "backlinks",
        target: this.target,
        mode: this.mode,
        limit: this.limit,
        order_by: "ahrefs_rank:desc",
        output: "json"
      },
    })).data
  },
}

Action Configuration

This component may be configured based on the props defined in the component code. Pipedream automatically prompts for input values in the UI.

LabelPropTypeDescription
AhrefsahrefsappThis component uses the Ahrefs app.
targettargetstring

Enter a domain or URL.

modemodestringSelect a value from the drop down menu:{ "label": "Exact", "value": "exact" }{ "label": "Domain", "value": "domain" }{ "label": "Subdomain", "value": "subdomains" }{ "label": "Prefix", "value": "prefix" }
limitlimitinteger

Action Authentication

Ahrefs uses OAuth authentication. When you connect your Ahrefs account, Pipedream will open a popup window where you can sign into Ahrefs and grant Pipedream permission to connect to your account. Pipedream securely stores and automatically refreshes the OAuth tokens so you can easily authenticate any Ahrefs API.

Pipedream requests the following authorization scopes when you connect your account:

api

About Ahrefs

SEO Tools & Resources

More Ways to Connect Ahrefs + Google Calendar

About Pipedream

Stop writing boilerplate code, struggling with authentication and managing infrastructure. Start connecting APIs with code-level control when you need it — and no code when you don't.

Into to Pipedream
Watch us build a workflow
Watch us build a workflow
7 min
Watch now ➜
"The past few weeks, I truly feel like the clichéd 10x engineer."
@heyellieday