meetroasEnglish
文件目录

Developer Hub/Partner Event API

Partner API v1 · Event catalog v2

Partner Event API

目标平台将一次业务事件送到 MeetROAS。请求必须使用账号凭证签章,并携带访客抵达目标站时取得的 mr_click_id。

1. 取得 Key ID 与 Secret

  1. 由 MeetROAS 客户账号的 Admin 登录 app.meetroas.com。
  2. 进入 Conversions → Destination partner API。
  3. 点击「建立凭证」。系统会显示 Key ID 与 Secret。
  4. 立即把两者存入目标平台的 secrets manager;Secret 离开页面后无法再次读取。

不需要向 MeetROAS 客服或合作窗口申请。客户账号 Admin 自助建立并透过安全渠道交给目标平台工程团队;遗失时由同一页面轮替。

2. 接收并保存 mr_click_id

MeetROAS 会把 mr_click_id 加到导向目标平台的 URL。目标平台必须在 landing request 读取它,并与该访客或交易的服务器端纪录关联保存。

Destination URL
https://casino.example/register?mr_click_id=mrc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • 不要解析、修改或自行产生这个值。
  • 不要把 Key ID 或 Secret 当作 mr_click_id。
  • mr_click_id 有效期为 30 天;未知、过期或不属于该账号的 ID 会回 404。

3. 建立请求标头与 HMAC 签章

Header由谁产生
Content-Typeapplication/json目标平台固定填写
X-MeetROAS-Keymrk_…MeetROAS 账号 Admin 在后端建立
X-MeetROAS-Timestamp发送当下的 10 位 Unix seconds目标平台每次请求产生
X-MeetROAS-Signaturev1=<64 lowercase hex>目标平台以 Secret 计算;不是另外申请的凭证
  1. 先把 JSON 序列化成最终 raw body。
  2. 产生 timestamp = floor(current_time_ms / 1000)。
  3. 签章输入必须是 timestamp + '.' + raw_body。
  4. 以 Secret 做 HMAC-SHA256,输出 lowercase hex,再加上 v1=。
签章输入(中间不可有空白或换行)
1786344000.{"event_id":"payment-123",...}

签章后必须原样发送同一个 body。任何重新缩排、字段排序或换行都会让签章失效。服务器时间必须与 UTC 相差不超过 5 分钟。

4. 验证整合

POST https://api.meetroas.com/v1/events/validate

验证端点使用与正式请求相同的凭证、标头、签章与 schema,但不会建立正式事件或触发流量平台转化。

选择你的服务器语言

四个范例都会序列化一次 body、签署相同 bytes,再发送到不会建立正式事件的验证端点。

Node.js 20+
Node.js 20+
import { createHmac, randomUUID } from "node:crypto";

const keyId = process.env.MEETROAS_KEY_ID;
const secret = process.env.MEETROAS_SECRET;
if (!keyId || !secret) throw new Error("Missing MeetROAS credentials");

const body = JSON.stringify({
  event_id: `test-${randomUUID()}`,
  mr_click_id: "mrc_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  event: "purchase",
  occurred_at: new Date().toISOString(),
  value: 49.95,
  currency: "USD",
});
const timestamp = String(Math.floor(Date.now() / 1000));
const digest = createHmac("sha256", secret)
  .update(`${timestamp}.${body}`, "utf8")
  .digest("hex");

const response = await fetch("https://api.meetroas.com/v1/events/validate", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-MeetROAS-Key": keyId,
    "X-MeetROAS-Timestamp": timestamp,
    "X-MeetROAS-Signature": `v1=${digest}`,
  },
  body,
});
console.log(response.status, await response.text());
Java 11+
Java 11+
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class MeetROASExample {
  public static void main(String[] args) throws Exception {
    String keyId = requireEnv("MEETROAS_KEY_ID");
    String secret = requireEnv("MEETROAS_SECRET");
    String body = String.format(
      "{\"event_id\":\"test-%s\",\"mr_click_id\":\"mrc_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"," +
      "\"event\":\"purchase\",\"occurred_at\":\"%s\",\"value\":49.95,\"currency\":\"USD\"}",
      UUID.randomUUID(), Instant.now());
    String timestamp = Long.toString(Instant.now().getEpochSecond());
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    byte[] digest = mac.doFinal((timestamp + "." + body).getBytes(StandardCharsets.UTF_8));
    StringBuilder hex = new StringBuilder();
    for (byte value : digest) hex.append(String.format("%02x", value & 0xff));
    HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.meetroas.com/v1/events/validate"))
      .header("Content-Type", "application/json")
      .header("X-MeetROAS-Key", keyId)
      .header("X-MeetROAS-Timestamp", timestamp)
      .header("X-MeetROAS-Signature", "v1=" + hex)
      .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8)).build();
    HttpResponse<String> response = HttpClient.newHttpClient()
      .send(request, HttpResponse.BodyHandlers.ofString());
    System.out.println(response.statusCode() + " " + response.body());
  }
  private static String requireEnv(String name) {
    String value = System.getenv(name);
    if (value == null || value.isBlank()) throw new IllegalStateException("Missing " + name);
    return value;
  }
}
Go 1.22+
Go 1.22+
package main

import (
  "bytes"
  "crypto/hmac"
  "crypto/sha256"
  "encoding/hex"
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "os"
  "strconv"
  "time"
)

type event struct {
  EventID string `json:"event_id"`
  ClickID string `json:"mr_click_id"`
  Event string `json:"event"`
  OccurredAt string `json:"occurred_at"`
  Value float64 `json:"value"`
  Currency string `json:"currency"`
}

func main() {
  keyID, secret := os.Getenv("MEETROAS_KEY_ID"), os.Getenv("MEETROAS_SECRET")
  if keyID == "" || secret == "" { panic("missing MeetROAS credentials") }
  body, err := json.Marshal(event{
    "test-" + strconv.FormatInt(time.Now().UnixNano(), 10), "mrc_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    "purchase", time.Now().UTC().Format(time.RFC3339), 49.95, "USD",
  })
  if err != nil { panic(err) }
  timestamp := strconv.FormatInt(time.Now().Unix(), 10)
  mac := hmac.New(sha256.New, []byte(secret))
  mac.Write(append([]byte(timestamp+"."), body...))
  req, err := http.NewRequest("POST", "https://api.meetroas.com/v1/events/validate", bytes.NewReader(body))
  if err != nil { panic(err) }
  req.Header.Set("Content-Type", "application/json")
  req.Header.Set("X-MeetROAS-Key", keyID)
  req.Header.Set("X-MeetROAS-Timestamp", timestamp)
  req.Header.Set("X-MeetROAS-Signature", "v1="+hex.EncodeToString(mac.Sum(nil)))
  response, err := http.DefaultClient.Do(req)
  if err != nil { panic(err) }
  defer response.Body.Close()
  responseBody, _ := io.ReadAll(response.Body)
  fmt.Printf("%d %s\n", response.StatusCode, responseBody)
}
C# · .NET 8+
C# · .NET 8+
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

var keyId = RequireEnv("MEETROAS_KEY_ID");
var secret = RequireEnv("MEETROAS_SECRET");
var body = JsonSerializer.Serialize(new {
  event_id = $"test-{Guid.NewGuid()}",
  mr_click_id = "mrc_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  @event = "purchase",
  occurred_at = DateTimeOffset.UtcNow.ToString("O"),
  value = 49.95,
  currency = "USD",
});
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var digest = hmac.ComputeHash(Encoding.UTF8.GetBytes($"{timestamp}.{body}"));
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.meetroas.com/v1/events/validate");
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
request.Headers.Add("X-MeetROAS-Key", keyId);
request.Headers.Add("X-MeetROAS-Timestamp", timestamp);
request.Headers.Add("X-MeetROAS-Signature", $"v1={Convert.ToHexString(digest).ToLowerInvariant()}");
using var response = await new HttpClient().SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");

static string RequireEnv(string name) => Environment.GetEnvironmentVariable(name)
  ?? throw new InvalidOperationException($"Missing {name}");
成功回应
{
  "valid": true,
  "authentication": "valid",
  "schema": "valid",
  "event": "purchase",
  "catalog_version": 2
}

客户账号 Admin 也可在建立凭证后,直接在 MeetROAS 后台点击「验证签章与格式」。

5. 发送正式事件

POST https://api.meetroas.com/v1/events

application/json
{
  "event_id": "test-084e192f-e80e-40ba-92f6-f7445307431c",
  "mr_click_id": "mrc_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  "event": "purchase",
  "occurred_at": "2026-08-10T16:11:06.330Z",
  "value": 49.95,
  "currency": "USD"
}
字段必要格式与语义
event_id目标平台产生的幂等键,1–128 字元,不含个人资料;同一业务事实重试必须沿用。
mr_click_id从目标 URL 保存的 opaque MeetROAS ID。
event必须来自下方接受事件清单。
occurred_atISO-8601 UTC,事件时间须在 31 天内。
value金融事件选填0–1,000,000,000 的十进位金额,最多六位小数;不是 minor units,MeetROAS 不换算。
currency选填ISO 4217 大写三码;提供 currency 时必须同时提供 value,MeetROAS 不猜币别。

同一 event_id 若改 event、occurred_at、mr_click_id、value 或 currency,会回 409。

6. 接受的事件

event 必须使用下列 key。请以机器可读 catalog 作为自动验证的权威来源。

事件 key何时发送重复规则event_id 规则金额
account_registration
完成注册
使用者已完成目标平台定义的账号注册流程;仅浏览页面或送出未完成的表单不算。once_per_click每次注册使用一个稳定 ID;重试时沿用同一个 ID。禁止
first_deposit
完成首充
账号的第一笔已验证充值已成功生效;pending 或失败交易不算。once_per_click每个账号只发送一次;重试时沿用同一个 ID。选填
deposit
完成充值
一笔已验证且成功生效的充值;这是可重复事件。repeatable每笔交易使用不同 ID;同一笔交易重试时沿用原 ID。选填
purchase
完成付款
一笔付款或购买已验证且成功结算;pending、失败或退款交易不算。repeatable每笔交易使用不同 ID;同一笔交易重试时沿用原 ID。选填

GET https://api.meetroas.com/v1/events/catalog

7. 支持的流量平台

无论客户使用哪个已支持的流量平台,目标平台都发送相同的 Partner Event API schema;不需要实现平台专用 postback。

状态流量平台说明
已支持TrafficStars, PropellerAds, RichAds, ExoClick客户须在 MeetROAS 后台完成对应流量平台账号与转化目标设定。
预计支持Adsterra, MGID, BidVertiser, HilltopAds, GeeMee, MGSkyAds尚未作为正式支持能力提供;请勿据此规划上线日期。

8. 回应、重试与上线

HTTP意义动作
200validate 成功,或正式事件为已处理的重复 event_id成功;不要产生新 event_id 重送
202正式事件首次接受成功;不要重送
400/415JSON、字段或 Content-Type 无效修正后再送
401Key、timestamp 或 signature 无效检查账号凭证、服务器时间与 raw body
404正式 endpoint 找不到 mr_click_id检查是否保存真实导向参数及是否已过期
409event_id 已对应不同业务事实停止自动重试并调查幂等逻辑
429/5xx暂时性限制或服务错误指数退避加抖动,保留原 event_id 与 body
  1. 取得凭证并通过验证端点。
  2. 使用测试访客产生的真实 mr_click_id 发送一笔正式事件。
  3. 请客户账号 Admin 在 MeetROAS 后台确认事件已收到,并完成流量平台设定。
  4. 确认后开始发送正式业务事件。

9. 安全要求

  • Secret 只能保存在服务器端 secrets manager。
  • 不得将 Secret 放入 URL、浏览器代码、工单或应用日志。
  • 每次请求使用新的 timestamp,并签署最终发送的原始 body。
  • 凭证遗失或疑似外泄时,由客户账号 Admin 立即轮替。旧 Key 会立即失效。