Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Mercaline2024/Ecomdrop-ia-connector-2/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Ecomdrop IA Connector uses Shopify OAuth 2.0 for authenticating merchants and accessing the Shopify Admin API. The app is configured as an AppStore distribution with session-based authentication.

Authentication Methods

1. Shopify Admin Authentication

Used for all admin-facing API endpoints that manage store data.

Implementation

The app uses @shopify/shopify-app-react-router to handle OAuth automatically:
// app/shopify.server.ts
import { shopifyApp } from "@shopify/shopify-app-react-router/server";

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET,
  apiVersion: ApiVersion.October25,
  scopes: process.env.SCOPES?.split(","),
  appUrl: process.env.SHOPIFY_APP_URL,
  authPathPrefix: "/auth",
  sessionStorage: new PrismaSessionStorage(prisma),
  distribution: AppDistribution.AppStore,
});

export const authenticate = shopify.authenticate;

Usage in Routes

All admin API routes authenticate using the authenticate.admin() method:
// app/routes/api.shopify.products.tsx
export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { session, admin } = await authenticate.admin(request);

  if (!session?.shop) {
    return new Response(JSON.stringify({ error: "No shop session found" }), {
      status: 401,
      headers: { "Content-Type": "application/json" }
    });
  }

  // Use admin.graphql() to make authenticated requests
  const response = await admin.graphql(`...`);
  // ...
};

Session Object

The authenticated session contains:
{
  id: string,              // Session ID
  shop: string,            // Store domain (e.g., "store.myshopify.com")
  state: string,           // OAuth state parameter
  isOnline: boolean,       // Online vs offline session
  scope: string,           // Granted scopes
  accessToken: string,     // API access token
  expires?: Date,          // Token expiration (online sessions)
}

Admin Client

The admin object provides GraphQL and REST API access:
// GraphQL queries/mutations
const response = await admin.graphql(
  `#graphql
    query getProducts {
      products(first: 250) {
        edges {
          node {
            id
            title
          }
        }
      }
    }
  `
);

const data = await response.json();
The app uses offline access tokens which don’t expire, allowing background operations like webhook processing.

2. Webhook Authentication

Shopify webhooks are authenticated using HMAC validation built into the SDK.
// app/routes/webhooks.orders.create.tsx
export const action = async ({ request }: ActionFunctionArgs) => {
  const { shop, session, topic, payload } = await authenticate.webhook(request);
  
  console.log(`Received ${topic} webhook for ${shop}`);
  
  // Shopify has already validated the HMAC signature
  // Process the webhook payload safely
  const order = payload.order;
  // ...
};

Webhook Security

Never process unauthenticated webhook data. Always use authenticate.webhook() which validates the X-Shopify-Hmac-SHA256 header.
The SDK automatically:
  • Validates HMAC signature
  • Verifies the webhook came from Shopify
  • Extracts shop domain from headers
  • Loads the shop’s session

3. External API Key Authentication

For callback endpoints that external services call (like Ecomdrop), the app uses API key authentication.
// app/routes/api.ecomdrop.callback.tsx
export const action = async ({ request }: ActionFunctionArgs) => {
  const body = await request.json();
  
  // Extract API key from request
  const apiKey = body.apiKey || body.api_key || body.token;
  
  if (!apiKey) {
    return new Response(
      JSON.stringify({ error: "API key is required" }),
      { status: 401 }
    );
  }
  
  // Validate against stored configuration
  const configuration = await db.shopConfiguration.findFirst({
    where: { ecomdropApiKey: apiKey }
  });
  
  if (!configuration) {
    return new Response(
      JSON.stringify({ error: "Invalid API key" }),
      { status: 401 }
    );
  }
  
  // Load shop session for API calls
  const shop = body.shop || configuration.shop;
  const sessionId = `offline_${shop}`;
  const session = await sessionStorage.loadSession(sessionId);
  // ...
};
Callback endpoints must be publicly accessible but secured with API key validation to prevent unauthorized access.

OAuth Flow

Installation Flow

Required Scopes

The app requests the following Shopify API scopes:
SCOPES=read_products,write_products,read_orders,write_orders,read_customers
ScopePurpose
read_productsFetch product catalog for synchronization
write_productsUpdate products with Dropi data
read_ordersAccess order data for Ecomdrop workflows
write_ordersUpdate order tags after processing
read_customersAccess customer info in order webhooks
Protected Customer Data: Accessing customer information requires Shopify’s approval. Development apps will see ACCESS_DENIED errors until the app is published and approved.

Session Storage

Sessions are persisted in PostgreSQL using Prisma:
// app/shopify.server.ts
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import prisma from "./db.server";

sessionStorage: new PrismaSessionStorage(prisma)
The Session table schema:
model Session {
  id          String    @id
  shop        String
  state       String
  isOnline    Boolean   @default(false)
  scope       String?
  expires     DateTime?
  accessToken String
  userId      BigInt?
}

Session Retrieval

import { sessionStorage } from "../shopify.server";

const sessionId = `offline_${shop}`;
const session = await sessionStorage.loadSession(sessionId);

if (!session?.accessToken) {
  throw new Error("No valid session found");
}

Making Authenticated API Calls

Using the Admin Client

The preferred method for GraphQL queries:
const { admin } = await authenticate.admin(request);

const response = await admin.graphql(
  `#graphql
    mutation updateProduct($input: ProductInput!) {
      productUpdate(input: $input) {
        product {
          id
          title
        }
        userErrors {
          field
          message
        }
      }
    }
  `,
  {
    variables: {
      input: {
        id: "gid://shopify/Product/123",
        title: "Updated Title"
      }
    }
  }
);

const data = await response.json();

Direct API Calls

For scenarios where the admin client isn’t available:
const apiVersion = "2025-10";
const endpoint = `https://${shop}/admin/api/${apiVersion}/graphql.json`;

const response = await fetch(endpoint, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Shopify-Access-Token": session.accessToken
  },
  body: JSON.stringify({
    query: `...`,
    variables: { ... }
  })
});
Always use the admin client when available. Direct fetch calls should only be used in special cases like callback endpoints.

Request Signing

Webhook HMAC Validation

Shopify signs all webhooks with HMAC-SHA256. The SDK handles validation automatically:
// Automatic validation - no manual code needed
const { shop, payload } = await authenticate.webhook(request);
Manual validation (if needed):
import crypto from "crypto";

function validateWebhook(body: string, hmacHeader: string): boolean {
  const hash = crypto
    .createHmac("sha256", process.env.SHOPIFY_API_SECRET!)
    .update(body, "utf8")
    .digest("base64");
    
  return hash === hmacHeader;
}

App Bridge Token Validation

For embedded app frontend requests, Shopify App Bridge handles token exchange:
// Frontend (React)
import { useAuthenticatedFetch } from "@shopify/app-bridge-react";

function MyComponent() {
  const fetch = useAuthenticatedFetch();
  
  // All requests automatically include session token
  const response = await fetch("/api/products");
}

Security Best Practices

Environment Variables: Never commit API keys or secrets to version control. Always use environment variables.

Configuration

# Required
SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SHOPIFY_APP_URL=https://your-app-domain.com
SCOPES=read_products,write_products,read_orders,write_orders

# Optional
SHOP_CUSTOM_DOMAIN=custom.domain.com

Validate All Input

// Always validate external input
const apiKey = body.apiKey;
if (!apiKey || typeof apiKey !== "string") {
  return new Response(
    JSON.stringify({ error: "Invalid API key format" }),
    { status: 400 }
  );
}

Use Session Validation

// Check session before processing
if (!session?.shop) {
  return new Response(
    JSON.stringify({ error: "No shop session found" }),
    { status: 401 }
  );
}

// Verify shop matches expected shop
if (session.shop !== expectedShop) {
  return new Response(
    JSON.stringify({ error: "Shop mismatch" }),
    { status: 403 }
  );
}

Secure Callback URLs

// Whitelist allowed callback origins
const ALLOWED_ORIGINS = [
  "https://panel.ecomdrop.app",
  "https://api.dropi.co"
];

const origin = request.headers.get("origin");
if (origin && !ALLOWED_ORIGINS.includes(origin)) {
  return new Response(
    JSON.stringify({ error: "Invalid origin" }),
    { status: 403 }
  );
}

Token Storage

Never expose access tokens in API responses or client-side code. Store them securely in the database and only use them server-side.

Error Handling

Common Authentication Errors

ErrorCauseSolution
No shop session foundMissing or expired sessionRe-authenticate via OAuth
Invalid API keyWrong Ecomdrop API keyUpdate configuration
ACCESS_DENIEDApp not approved for protected dataPublish app and request Shopify approval
UnauthorizedMissing authentication headerInclude session token or API key
ForbiddenValid auth but wrong permissionsCheck OAuth scopes

Session Recovery

// Handle expired sessions gracefully
try {
  const { session, admin } = await authenticate.admin(request);
  // ...
} catch (error) {
  if (error.message.includes("session")) {
    // Redirect to re-authenticate
    return redirect("/auth/login");
  }
  throw error;
}

Testing Authentication

Local Development

  1. Use Shopify CLI for local testing:
npm run dev
  1. The CLI creates a tunnel and handles OAuth automatically
  2. Test with development store

Testing Webhooks

# Trigger test webhook from Shopify CLI
shopify webhook trigger --topic orders/create

Testing Callbacks

Use tools like curl or Postman with valid API key:
curl -X POST https://your-app.com/api/ecomdrop/callback \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "your_ecomdrop_api_key",
    "orderName": "#1014",
    "status": "success",
    "tag": "processed"
  }'

Next Steps

API Overview

Explore available endpoints and data structures

Webhooks

Set up webhook handlers for events

Build docs developers (and LLMs) love