رُتبة CRM

Google Ads

أضف اسم الحملة إلى نقرات Google Ads، وأرسل التكلفة اليومية، وارفع المبيعات التي جاءت من نقرات Google تحويلات بلا اتصال.

يتم ربط Google Ads على جزأين. الأول إعداد واحد في الحساب يضيف اسم الحملة إلى كل نقرة على إعلان، فتحمله الزيارات والاستفسارات وطلبات المتجر. والثاني سكربت صغير في Google Ads يرسل التكلفة اليومية لكل حملة إلى رُتبة CRM، ويرفع إلى Google المبيعات التي جاءت من نقرة Google. عندها تعرض عائد الحملات لكل حملة ما كلّفته وما أعادته، وتتعلم مزايدة Google من المشترين الحقيقيين. وما يعنيه الرفع لإعلاناتك مشروح في الإعلانات.

قبل أن تبدأ

  • من يستطيع ذلك: مالك الشركة أو أحد مسؤوليها، ممن يدير حساب Google Ads أيضًا.
  • الباقة: متاح عندما تشمل باقتك عائد الحملات وواجهة API.
  • طلبات تحمل الحملات: فعّل Order Attribution في WooCommerce (راجع WooCommerce). تُنسب الطلبات والمبيعات إلى الحملة من بيانات الطلب نفسه فقط. أما مقتطف التتبع فيضيف رقم الزوار، ولا ينسب الطلبات.
  • العملة: يجب أن تكون عملة حساب Google Ads واحدة من USD وEUR وGBP وTRY وSAR وAED. ولحساب العائد يجب أن تكون أيضًا عملة مبيعاتك.

الطريقة الأسهل: اربط بـGoogle

حين تتوفر في منصتك، تعرض بطاقة إعلانات Google في صفحة الإعلانات زر اربط بـGoogle. وهو يغني عن كل الخطوات التالية.

  1. اضغط اربط بـGoogle وسجّل الدخول بحساب Google الذي يملك إعلاناتك. يطلب Google السماح بالوصول إلى إعلاناتك: اضغط سماح.
  2. تحت أي حساب إعلانات Google؟ اختر الحساب. والحسابات التي تصل إليها عبر حساب مدير يُكتب بجانبها عبر حساب مدير.
  3. تحت أين تُحتسب المبيعات؟ اضغط أنشئها لي: ينشئ رُتبة CRM إجراءي التحويل CRM purchase وCRM lead في حسابك. أو اختر إجراءات موجودة عندك.

وهذا كل شيء. بعدها يوسم رُتبة CRM كل حملة باسمها، ويستلم تكلفة كل حملة كل ليلة، ويرسل مبيعاتك التي لها نقرة من Google كل ساعة. وتعرض البطاقة الحساب، ومتى وصلت التكاليف آخر مرة، ومتى أُرسلت المبيعات آخر مرة. وإن رفض Google عملية بيع، كأن تكون النقرة قديمة جدًا، تنتظر مع سبب Google وتُعاد المحاولة.

وإن سحبت الإذن من حساب Google، تعرض البطاقة متوقف ويتلقى المالكون والمديرون تنبيهًا، فاضغط اربط من جديد. وزر قطع الربط يزيل الوصول من Google أيضًا، وتبقى التكاليف والمبيعات التي وصلت. وما دمت مربوطًا بهذه الطريقة لا يستلم السكربت أي مبيعات، فلا يُرسل شيء مرتين.

وإن لم يسمح Google لـرُتبة CRM بوسم الحساب، تعرض البطاقة لاحقة الرابط النهائي لتلصقها بنفسك، مع زر نسخ.

أو استخدم السكربت

الخطوات التالية تُعد سكربت إعلانات Google بدلًا من ذلك. استخدمها حين لا يظهر زر اربط بـGoogle، أو إن كنت تفضّل السكربت.

الخطوة 1: أنشئ مفتاح API للسكربت

  1. من القائمة الجانبية افتح جهات الاتصال، ثم اضغط مفاتيح API.
  2. اضغط إنشاء مفتاح، وسمِّه «Google Ads»، واختر قراءة وكتابة في الصلاحية.
  3. انسخ المفتاح الآن، فلن يظهر مرة أخرى.

يعمل المفتاح باسمك وبدورك، فأنشئه وأنت داخل بحساب مالك أو مسؤول. واحتفظ به في السكربت فقط، فمن يملكه يستطيع كتابة تكاليف إعلانات لشركتك وقراءة المبيعات التي تنتظر الرفع إلى Google. والمزيد عن المفاتيح في مفاتيح API.

الخطوة 2: أضف الحملة إلى كل نقرة

  1. في Google Ads افتح Admin ثم Account settings ثم Tracking.
  2. أبقِ Auto-tagging مفعّلًا.
  3. في Final URL suffix اكتب:
utm_source=google&utm_medium=cpc&utm_campaign={_campaign}
  1. احفظ.

{_campaign} معامل مخصص يعطيه السكربت في الخطوة 3 لكل حملة: نسخة نظيفة من اسم الحملة. ويبقى فارغًا حتى يعمل السكربت أول مرة.

الخطوة 3: أضف السكربت

  1. في Google Ads افتح Tools ثم Bulk actions ثم Scripts، واضغط + لإضافة سكربت جديد.
  2. احذف الكود النموذجي والصق السكربت أدناه.
  3. ضع المفتاح من الخطوة 1 مكان PASTE_THE_WRITE_API_KEY_HERE. اكتبه أو الصقه بنفسك، ولا ترسله في أي محادثة أو بريد.
  4. اضغط Authorize واسمح بالوصول إلى حساب Google Ads.
  5. اضغط Run، وعندما ينتهي افتح السجل (Logs).
  6. اجعل تكرار السكربت Daily.

إن كنت ثبّت نسخة سابقة من السكربت فاستبدل كودها بهذا، فهذه النسخة وحدها ترفع المبيعات. وأبقِ مفتاحك فيه.

/**
 * رُتبة CRM — Google Ads script.
 *
 * Put it in Google Ads: Tools > Bulk actions > Scripts > + > paste, authorise, then schedule it "Daily".
 * It does three things, and changes nothing else in the account:
 *   1. Gives every campaign a custom parameter "campaign" with a clean name, so the account's final URL
 *      suffix  utm_source=google&utm_medium=cpc&utm_campaign={_campaign}  tags every visit and order.
 *   2. Sends each campaign's cost for the last 30 days to the CRM, so campaign returns show cost and sales
 *      side by side. Sending a day again replaces it; nothing is counted twice.
 *   3. Uploads the sales that came from a Google click as offline conversions, when the CRM's Advertising page
 *      has "Send sales to Google Ads" on, so Google's bidding learns from real buyers.
 *
 * Fill in the two values below. The key is an API key with write access, created by an owner or admin in the
 * CRM under API keys. Keep it only here: anyone with it can write ad spend for your company.
 */
var CRM_URL = 'https://rutbacrm.com/api/v1/ad-spend';
var API_KEY = 'PASTE_THE_WRITE_API_KEY_HERE';
var DAYS = 'LAST_30_DAYS';

function clean(name) {
  return String(name).toLowerCase().replace(/[^a-z0-9؀-ۿ]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 100) || 'campaign';
}

function tag(iterator) {
  var changed = 0;
  while (iterator.hasNext()) {
    var campaign = iterator.next();
    var params = campaign.urls().getCustomParameters() || {};
    var value = clean(campaign.getName());
    if (params.campaign !== value) {
      params.campaign = value;
      campaign.urls().setCustomParameters(params);
      changed++;
    }
  }
  return changed;
}

function main() {
  sendCosts();
  uploadSales();
}

// Sales and leads that came from a Google click, uploaded as offline conversions so smart bidding learns from them.
// Nothing happens until "Send sales to Google Ads" is on in the CRM's Advertising page.
function uploadSales() {
  var base = CRM_URL.replace(/\/ad-spend$/, '');
  var answer = UrlFetchApp.fetch(base + '/ad-conversions', { method: 'get', muteHttpExceptions: true, headers: { Authorization: 'Bearer ' + API_KEY, Accept: 'application/json' } });
  if (answer.getResponseCode() !== 200) {
    Logger.log('Sales: CRM answered ' + answer.getResponseCode() + ': ' + answer.getContentText());
    return;
  }
  var sales = JSON.parse(answer.getContentText()).data || [];
  if (sales.length === 0) {
    Logger.log('Sales: nothing new to upload.');
    return;
  }
  var upload = AdsApp.bulkUploads().newCsvUpload(['Google Click ID', 'Conversion Name', 'Conversion Time', 'Conversion Value', 'Conversion Currency'], { moneyInMicros: false, timeZone: 'Etc/GMT' });
  upload.forOfflineConversions();
  sales.forEach(function (sale) {
    upload.append({ 'Google Click ID': sale.gclid, 'Conversion Name': sale.name, 'Conversion Time': sale.time, 'Conversion Value': sale.value || '', 'Conversion Currency': sale.currency || '' });
  });
  upload.apply();
  var done = UrlFetchApp.fetch(base + '/ad-conversions/confirm', {
    method: 'post', contentType: 'application/json', muteHttpExceptions: true,
    headers: { Authorization: 'Bearer ' + API_KEY }, payload: JSON.stringify({ ids: sales.map(function (sale) { return sale.id; }) })
  });
  Logger.log('Sales: uploaded ' + sales.length + ' to Google. CRM answered ' + done.getResponseCode() + ': ' + done.getContentText());
}

function sendCosts() {
  var currency = AdsApp.currentAccount().getCurrencyCode();
  var tagged = tag(AdsApp.campaigns().withCondition('campaign.status != REMOVED').get());
  if (AdsApp.performanceMaxCampaigns) {
    tagged += tag(AdsApp.performanceMaxCampaigns().withCondition('campaign.status != REMOVED').get());
  }
  if (AdsApp.shoppingCampaigns) {
    tagged += tag(AdsApp.shoppingCampaigns().withCondition('campaign.status != REMOVED').get());
  }

  var rows = [];
  var report = AdsApp.search('SELECT campaign.name, segments.date, metrics.cost_micros FROM campaign WHERE segments.date DURING ' + DAYS + ' AND metrics.cost_micros > 0');
  while (report.hasNext()) {
    var row = report.next();
    rows.push({ campaign: clean(row.campaign.name), source: 'google', spent_on: row.segments.date, amount: (Number(row.metrics.costMicros) / 1000000).toFixed(2), currency: currency });
  }
  if (rows.length === 0) {
    Logger.log('Tagged ' + tagged + ' campaigns. No cost to send.');
    return;
  }
  Logger.log('Tagged ' + tagged + ' campaigns. Sending ' + rows.length + ' rows of daily cost.');
  for (var i = 0; i < rows.length; i += 1000) {
    var response = UrlFetchApp.fetch(CRM_URL, {
      method: 'post', contentType: 'application/json', muteHttpExceptions: true,
      headers: { Authorization: 'Bearer ' + API_KEY }, payload: JSON.stringify({ rows: rows.slice(i, i + 1000) })
    });
    Logger.log('CRM answered ' + response.getResponseCode() + ': ' + response.getContentText());
  }
}

ماذا يفعل السكربت

  • يعطي الحملات أسماء نظيفة. كل حملة غير محذوفة، ومنها Performance Max وShopping، تحصل على المعامل المخصص campaign: اسمها بأحرف صغيرة، مع تحويل المسافات والرموز إلى شرطات، حتى 100 حرف. فتصبح «Autumn Sale 2026» هكذا autumn-sale-2026، وتبقى الأحرف العربية كما هي.
  • يرسل التكلفة. يرسل لآخر 30 يومًا تكلفة كل حملة في كل يوم فيه تكلفة. وإرسال اليوم نفسه مرة أخرى يستبدله، فلا يُحسب شيء مرتين، وتصل التصحيحات التي يجريها Google Ads.
  • يرفع المبيعات. يسأل رُتبة CRM عن المبيعات والعملاء المحتملين الذين جاؤوا من نقرة Google ولم يُرفعوا بعد، فيرفعهم إلى Google تحويلات بلا اتصال، ثم يؤكدهم فلا يُعرضون مرة أخرى. ولا يُرفع شيء حتى يعمل أرسل المبيعات إلى Google Ads، كما في القسم التالي.

الخطوة 4: تأكد من عمله

  1. في سجل السكربت ابحث عن CRM answered 200، ويليه عدد الصفوف المحفوظة saved وعدد التي لم تتغير unchanged. والحساب الذي فيه حملات كثيرة يرسل تكاليفه على دفعات، كل دفعة حتى 1000 صف ولها سطرها في السجل.
  2. في رُتبة CRM افتح التقارير ثم عائد الحملات. تعرض كل حملة التكلفة بجانب الطلبات والمبيعات والعائد وتكلفة الطلب.
  3. افتح موقعك من أحد إعلاناتك، أو مع ?utm_campaign= والاسم النظيف لحملة، وتأكد أن الزيارة أو الطلب يحمل تلك الحملة.

رفع المبيعات تحويلات بلا اتصال

يستطيع السكربت نفسه أن يخبر Google أي النقرات صارت مبيعات، فتصرف المزايدة الذكية ميزانيتك على عمليات البحث التي تجلب مشترين.

  1. في Google Ads افتح الأهداف، ثم التحويلات، ثم إجراء تحويل جديد، ثم استيراد، ثم CRM أو ملفات أخرى، واختر التحويلات من النقرات. سمّه مثلًا «CRM purchase». وفي الواجهة الإنجليزية: Goals ثم Conversions ثم New conversion action ثم Import ثم CRM or other files.
  2. في رُتبة CRM افتح الإعلانات من القائمة الجانبية. وفي بطاقة إعلانات Google اكتب الاسم نفسه حرفيًا في اسم التحويل للمبيعات، وضع علامة على أرسل المبيعات إلى Google Ads واضغط حفظ. وإجراء تحويل ثانٍ للعملاء المحتملين في اسم التحويل للعملاء المحتملين اختياري.
  3. تأكد أن موقعك يحمل مقتطف التتبع، ليُسجَّل نقر Google حين يصل الزائر من إعلان.
  4. استعمل السكربت الموجود في هذه الصفحة، بتكرار Daily.

في كل تشغيل يومي:

  1. يجلب حتى 500 بيع منتظر من https://rutbacrm.com/api/v1/ad-conversions، الأقدم أولًا: معرّف نقرة Google (gclid)، واسم التحويل، والوقت بتوقيت UTC، وللبيع قيمته وعملته.
  2. يرفعها إلى Google في ملف تحويلات بلا اتصال واحد، ويكتب في السجل Sales: uploaded N to Google.
  3. يؤكدها في https://rutbacrm.com/api/v1/ad-conversions/confirm، فيُرفع كل بيع مرة واحدة. وينتهي سطر السجل بـCRM answered 200 وعدد confirmed.

يجب أن يطابق اسم التحويل في رُتبة CRM إجراء التحويل في Google Ads حرفًا بحرف. ولا تُعرض إلا المبيعات التي كانت نقرة Google قبلها بـ90 يومًا على الأكثر، ولا النقرات إلا التي تحمل gclid. وتعرض بطاقة إعلانات Google في صفحة الإعلانات رُفع: N · بانتظار السكربت: N وتاريخ آخر رفع. والمساران موصوفان في واجهة تحويلات الإعلانات.

كيف يُحسب العائد

الرقم المعنى
الزوار زوار جاؤوا أول مرة من الحملة، إن كان التتبع مفعّلًا
الطلبات والمبيعات طلبات المتجر بحالات البيع، حسب تاريخ الطلب
عملاء جدد عملاء كان أول طلب لهم ضمن الفترة
التكلفة التكاليف المسجلة للحملة في الفترة
العائد المبيعات مقسومة على التكلفة، مثل 3.2×. وهو إيراد لا ربح
تكلفة الطلب التكلفة مقسومة على الطلبات
  • تُطابق الحملات بالاسم، دون اعتبار لحالة الأحرف والمسافات في الطرفين، ولهذا يستخدم السكربت والإعداد الاسم النظيف نفسه.
  • كل عملة صف مستقل، ولا تحويل بين العملات. إن كانت التكلفة بعملة والمبيعات بأخرى يظهر العائد «—» لا صفرًا.
  • الطلبات بلا حملة تظهر تحت بدون حملة، حتى تبقى المجاميع صادقة.
  • اختر 30 أو 90 أو 365 يومًا من أعلى الصفحة.

طرق أخرى لإضافة التكاليف

  • يدويًا: في عائد الحملات يستطيع المالك والمسؤولون استخدام إضافة تكلفة حملة، وكتابة الحملة كما في utm_campaign تمامًا. وزر إلغاء يُخرج الإدخال الخاطئ من المجاميع ومن آخر التكاليف، ويبقى محفوظًا دون حذف.
  • من أداة أخرى: أرسل POST https://rutbacrm.com/api/v1/ad-spend مع الترويسة Authorization: Bearer YOUR_API_KEY وجسم JSON بصيغة {"rows": [...]} فيه من 1 إلى 1000 صف. في كل صف campaign (حتى 120 حرفًا)، وsource اختياري، وspent_on بصيغة YYYY-MM-DD (ليس في المستقبل، ولا أقدم من 399 يومًا)، وamount بخانتين عشريتين على الأكثر، وcurrency. الصف للحملة واليوم والمصدر والعملة نفسها يستبدل السابق، والمبلغ 0 يزيله. ويعطي الرد عدد الصفوف saved وunchanged. وإن كان أي صف غير صالح فلا يُحفظ شيء. راجع REST API.

حل المشكلات

في سجل السكربت ما تفعله
CRM answered 401 المفتاح خاطئ أو أُلغي. أنشئ مفتاحًا جديدًا والصقه من جديد
CRM answered 403 المفتاح للقراءة فقط، أو منشئه ليس مالكًا ولا مسؤولًا، أو يمنع دور مخصص منشئه من واجهة API أو عائد الحملات، أو الباقة لا تشمل عائد الحملات أو واجهة API
CRM answered 422 رُفض صف، وغالبًا لأن عملة الحساب غير مدعومة
No cost to send. لم تكن لأي حملة تكلفة في آخر 30 يومًا. وتُرفع المبيعات مع ذلك في التشغيل نفسه
Sales: nothing new to upload. لا بيع من نقرة Google ينتظر، أو أرسل المبيعات إلى Google Ads متوقف
Sales: CRM answered 403 منشئ المفتاح ليس مالكًا ولا مسؤولًا (Only owners and administrators send results to ad platforms.)، أو يمنعه دور مخصص من عائد الحملات، أو الباقة لا تشمل عائد الحملات
لا سطر يبدأ بـSales: السكربت نسخة قديمة. استبدله بالموجود في هذه الصفحة
تظهر التكاليف لكن المبيعات تحت بدون حملة راجع Final URL suffix، وتأكد من تفعيل Order Attribution في WooCommerce. مقتطف التتبع لا ينسب الطلبات إلى الحملات

آخر تحديث 2026-11-08