Partner API v1 · Event catalog v2
Partner Event API
目标平台将一次业务事件送到 MeetROAS。请求必须使用账号凭证签章,并携带访客抵达目标站时取得的 mr_click_id。
1. 取得 Key ID 与 Secret
- 由 MeetROAS 客户账号的 Admin 登录 app.meetroas.com。
- 进入 Conversions → Destination partner API。
- 点击「建立凭证」。系统会显示 Key ID 与 Secret。
- 立即把两者存入目标平台的 secrets manager;Secret 离开页面后无法再次读取。
不需要向 MeetROAS 客服或合作窗口申请。客户账号 Admin 自助建立并透过安全渠道交给目标平台工程团队;遗失时由同一页面轮替。
2. 接收并保存 mr_click_id
MeetROAS 会把 mr_click_id 加到导向目标平台的 URL。目标平台必须在 landing request 读取它,并与该访客或交易的服务器端纪录关联保存。
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-Type | application/json | 目标平台固定填写 |
X-MeetROAS-Key | mrk_… | MeetROAS 账号 Admin 在后端建立 |
X-MeetROAS-Timestamp | 发送当下的 10 位 Unix seconds | 目标平台每次请求产生 |
X-MeetROAS-Signature | v1=<64 lowercase hex> | 目标平台以 Secret 计算;不是另外申请的凭证 |
- 先把 JSON 序列化成最终 raw body。
- 产生 timestamp = floor(current_time_ms / 1000)。
- 签章输入必须是 timestamp + '.' + raw_body。
- 以 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+
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+
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+
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+
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
{
"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_at | 是 | ISO-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。 | 选填 |
7. 支持的流量平台
无论客户使用哪个已支持的流量平台,目标平台都发送相同的 Partner Event API schema;不需要实现平台专用 postback。
| 状态 | 流量平台 | 说明 |
|---|---|---|
| 已支持 | TrafficStars, PropellerAds, RichAds, ExoClick | 客户须在 MeetROAS 后台完成对应流量平台账号与转化目标设定。 |
| 预计支持 | Adsterra, MGID, BidVertiser, HilltopAds, GeeMee, MGSkyAds | 尚未作为正式支持能力提供;请勿据此规划上线日期。 |
8. 回应、重试与上线
| HTTP | 意义 | 动作 |
|---|---|---|
200 | validate 成功,或正式事件为已处理的重复 event_id | 成功;不要产生新 event_id 重送 |
202 | 正式事件首次接受 | 成功;不要重送 |
400/415 | JSON、字段或 Content-Type 无效 | 修正后再送 |
401 | Key、timestamp 或 signature 无效 | 检查账号凭证、服务器时间与 raw body |
404 | 正式 endpoint 找不到 mr_click_id | 检查是否保存真实导向参数及是否已过期 |
409 | event_id 已对应不同业务事实 | 停止自动重试并调查幂等逻辑 |
429/5xx | 暂时性限制或服务错误 | 指数退避加抖动,保留原 event_id 与 body |
- 取得凭证并通过验证端点。
- 使用测试访客产生的真实 mr_click_id 发送一笔正式事件。
- 请客户账号 Admin 在 MeetROAS 后台确认事件已收到,并完成流量平台设定。
- 确认后开始发送正式业务事件。
9. 安全要求
- Secret 只能保存在服务器端 secrets manager。
- 不得将 Secret 放入 URL、浏览器代码、工单或应用日志。
- 每次请求使用新的 timestamp,并签署最终发送的原始 body。
- 凭证遗失或疑似外泄时,由客户账号 Admin 立即轮替。旧 Key 会立即失效。