Webhooks文档
1. 介绍
Webhook是一种消息订阅异步接口实现方式,它能够将用户订阅消息数据及时的传输到用户指定的位置(URL):将用户在灵动营销平台上发给用户会员的邮件发送状态,会员打开邮件,会员点击邮件里面链接,更新会员信息等行为事件信息数据及时的传回用户自己的业务系统。
- 例如有下面使用场景:
- 1.用户系统调用灵动平台邮件发送接口;
- 2.灵动平台处理邮件投递后,将投递结果通过HTTP、HTTPS的POST推送给用户系统(Webhook订阅地址);
- 3.用户系统接收推送结果后,向请求方(灵动平台)同步返回“200”确认处接收结果处理正常。
2. Webhook推送消息实体说明
-
名称 意义 数据类型 长度 备注 replies 回复内容消息 Json Array 没有上线,与新版本自动化一起上线 delivered 投递通知消息 Json Array 未来将会废弃,名称改为receipts opens 打开通知消息 Json Array clicks 点击通知消息 Json Array changeProfiles 会员属性更新消息 Json Array 没有上线,与新版本自动化一起上线 subscriptions 订阅通知消息 Json Array 没有上线,与新版本自动化一起上线 unsubscriptions 退订通知消息 Json Array complaints 投诉通知消息 Json Array source 消息来源 String 10 值 意义 esp 用户通过ESP(EDM服务平台)退订投诉链接提交的申请 isp 用户在邮箱Web端,点击退订按钮,或点击这是垃圾邮件按钮,由ISP通知给ESP content 投诉内容 String 100 id 数据主键 Long 系统链接主键为负数,用以区分普通链接 campaign 邮件名称 String 100 email 邮件地址 String 100 uid 标识 String 100 mid 邮件消息标识,可从调用发送接口返回值中获取,和投递通知消息中的id一致 String 100 status 送达状态 String 32 值 意义 success 已发送 bounce:bad-mailbox 无效地址 bounce:soft 软弹回 bounce:hard 硬弹回 dsn 发送错误原因 String 200 updatedAt 投递完成时间 Timestamp 自1970年1月1日(00:00:00 GMT)至当前时间的毫秒数 createdAt 数据生成时间 Timestamp 自1970年1月1日(00:00:00 GMT)至当前时间的毫秒数 trackingId 用户自定义文本标识 String 60 灵动平台在处理完投递后,可以通过Webbook传回的消息中获取该数据。当一次调用发送多封邮件时,请用逗号分割。 trackingData 用户自定义文本数据 String 1000 灵动平台在处理完投递后,可以通过Webbook传回消息中,获取该数据,数据中双引号会被转移义 updateTime 投递完成时间(未来将废弃) Timestamp 自1970年1月1日(00:00:00 GMT)至当前时间的毫秒数 outTime 数据生成时间(未来将废弃) Timestamp 自1970年1月1日(00:00:00 GMT)至当前时间的毫秒数 id 标识 Long 投递通知消息中的id与发送接口中返回值和其它消息中mid一致,能够来追踪单封邮件打开,点击,投诉等用户行为 contactId 标识 Long 联系人标识 scheduleId 标识 Long 发送计划标识 messageId 标识 Long 消息(邮件)模板标识 ip 地址 String 32 IPV4 location 国家地区/省/市 String 100 longitude 经度 Double latitude 纬度 Double device 客户端设备 String 100 platform 客户端操作系统 String 100 client 客户端软件 String 100 url 用户点击的链接 String 500 - 系统链接#开头,如:
- #退订
- #投诉
- #网页版本
- #用户中心
- #推荐好友
- #注册 #确认
subject 邮件主题 String 200
3. Webhook推送消息实体示例:
-
//备注:下面示例数据在已经做了混淆:) { "delivered": [//未来将会废弃,名称改为receipts { "id": 22201602002317593, "campaign": "大麦网温馨提示:您关注的演出内容有更新", "uid": "GFpCmAhnXpPoXqXLH2BY", "email": "hg007@example.com", "status": "bounce:hard", "dsn": "Action: failed | Status: 5.1.2 (bad destination system: no such domain)", "updatedAt": 1392890885000,//2014-02-20 18:08:05 "createdAt": 1392890885000,//2014-02-20 18:08:05 "scheduleId": 17436, "messageId": 12399, "contactId": 19016004, "trackingId": "1axfm7ht2m8k", "trackingData": "{ \"message\":\"success\", \"id\": [ 426034,41767,28341,53326 ] }" }, { "id": 22201606019253710, "campaign": "大麦网温馨提示:您关注的演出内容有更新", "uid": "GFpCmAhnXpPoXqXLH2BY", "email": " xiaorong@example.com ", "status": "success", "dsn": "smtp;250 ok: Message 1758717523 accepted", "updatedAt": 1403852584000,//2014-06-27 15:03:04 "createdAt": 1403852540000,//2014-06-27 15:02:20 "scheduleId": 121830, "messageId": 162084, "contactId": 43001513, "trackingId": "1axfm7ht2m8k", "trackingData": "{ \"message\":\"success\", \"id\": [ 426034,41767,28341,53326 ] }" } ], "replies": [//回复内容,与新版本自动化一起上线 { "id": 22201606019253710, "campaign": "大麦网温馨提示:您关注的演出内容有更新", "uid": "GFpCmAhnXpPoXqXLH2BY", "email": " xiaorong@example.com ", "content": "请与我联系", "updatedAt": 1403852584000,//2014-06-27 15:03:04 本次回复时间 "createdAt": 1403852540000,//2014-06-27 15:02:20 第一次回复时间 "scheduleId": 121830, "messageId": 162084, "contactId": 43001513, "trackingId": "1axfm7ht2m8k", "trackingData": "{ \"message\":\"success\", \"id\": [ 426034,41767,28341,53326 ] }" } ], "clicks": [ { "id": 22201603016582230, "mid": 22201602002317593, "campaign": "大麦网温馨提示:您关注的演出内容有更新", "subject": "大麦网温馨提示:您关注的演出内容有更新", "uid": "GFpCmAhnXpPoXqXLH2BY", "email": "someone@example.com", "ip": "119.255.75.126", "location": "中国/河北/廊坊", "longitude": 116.7139, "latitude": 39.5292, "device": "", "platform": "Windows 7", "client": "Chrome", "url": "http://item.damai.cn/78982.html", "createdAt": 1392890885000,//2014-02-20 18:08:05 "trackingId": "1axfm7ht2m8k", "trackingData": "{ \"message\":\"success\", \"id\": [ 426034,41767,28341,53326 ] }" }, { "id": 22201603026582230, "mid": 22201602002317593, "campaign": "大麦网温馨提示:您关注的演出内容有更新", "subject": "大麦网温馨提示:您关注的演出内容有更新", "uid": "GFpCmAhnXpPoXqXLH2BY", "email": "someone@example.com", "ip": "119.255.75.126", "location": "中国/河北/廊坊", "longitude": 116.7139, "latitude": 39.5292, "device": "", "platform": "Windows 7", "client": "Chrome", "url": "#退订", "createdAt": 1392890885000,//2014-02-20 18:08:05 "trackingId": "1axfm7ht2m8k", "trackingData": "{ \"message\":\"success\", \"id\": [ 426034,41767,28341,53326 ] }" } ], "opens": [ { "id": 222016111000001234, "mid": 22201602002317593, "campaign": "大麦网温馨提示:您关注的演出内容有更新", "subject": "大麦网温馨提示:您关注的演出内容有更新", "uid": "GFpCmAhnXpPoXqXLH2BY", "email": "someone@example.com", "ip": "119.255.75.126", "location": "中国/河北/廊坊", "longitude": 116.7139, "latitude": 39.5292, "device": "", "platform": "Windows 7", "client": "Chrome", "createdAt": 1392890885000,//2014-02-20 18:08:05 "trackingId": "1axfm7ht2m8k", "trackingData": "{ \"message\":\"success\", \"id\": [ 426034,41767,28341,53326 ] }" } ], "changeProfiles": [//与新版本自动化一起上线 { "id": 22201911285849505, "mid": 22201602002317593, "campaign": "大麦网温馨提示:您关注的演出内容有更新", "subject": "大麦网温馨提示:您关注的演出内容有更新", "uid": "GFpCmAhnXpPoXqXLH2BY", "email": "example@abc.com", "properties": [//会员属性数据 { "mobile": 13610243072 }, { "brithDay": "1999-02-05", }, { "gender": "female", }, { "nickName": "Aerith" } ], "changes": [//发生改变的属性 "mobile", "nickName" ], "addLists": [//加入列表 "接受邀请客户" ], "leaveLists": [//移出列表 "待邀请客户" ], "status":"active",//deleted 删除, unconfirmed 未确认,active 活动,invalidation 无效,unsubscription 退订 "ip": "119.255.75.126", "location": "中国/河北/廊坊", "longitude": 116.7139, "latitude": 39.5292, "device": "", "platform": "Windows 7", "client": "Chrome", "source": "form", "createdAt": 1392890885000,//2014-02-20 18:08:05 "trackingId": "1axfm7ht2m8k", "trackingData": "{ \"message\":\"success\", \"id\": [ 426034,41767,28341,53326 ] }" } ], "subscriptions": [//与新版本自动化一起上线 { "id": 22201911285849505, "mid": 22201602002317593, "campaign": "大麦网温馨提示:您关注的演出内容有更新", "subject": "大麦网温馨提示:您关注的演出内容有更新", "uid": "GFpCmAhnXpPoXqXLH2BY", "email": "example@abc.com", "properties": [//会员属性数据 { "mobile": 13610243072 }, { "brithDay": "1999-02-05", }, { "gender": "female", }, { "nickName": "Aerith" } ], "changes": [//发生改变的属性 "mobile", "nickName" ], "addLists": [//加入列表 "接受邀请客户", "新会员注册" ], "leaveLists": [//移出列表 ], "status":"active",//deleted 删除, unconfirmed 未确认,active 活动,invalidation 无效,unsubscription 退订 "ip": "119.255.75.126", "location": "中国/河北/廊坊", "longitude": 116.7139, "latitude": 39.5292, "device": "", "platform": "Windows 7", "client": "Chrome", "source": "form", "createdAt": 1392890885000,//2014-02-20 18:08:05 "trackingId": "1axfm7ht2m8k", "trackingData": "{ \"message\":\"success\", \"id\": [ 426034,41767,28341,53326 ] }" } ], "unsubscriptions": [ { "id": 22201911285849505, "mid": 22201602002317593, "campaign": "大麦网温馨提示:您关注的演出内容有更新", "subject": "大麦网温馨提示:您关注的演出内容有更新", "uid": "GFpCmAhnXpPoXqXLH2BY", "email": "example@abc.com", "ip": "119.255.75.126", "location": "中国/河北/廊坊", "longitude": 116.7139, "latitude": 39.5292, "device": "", "platform": "Windows 7", "client": "Chrome", "source": "esp", "content": "I do not want to receive these emails anymore .", "createdAt": 1392890885000,//2014-02-20 18:08:05 "trackingId": "1axfm7ht2m8k", "trackingData": "{ \"message\":\"success\", \"id\": [ 426034,41767,28341,53326 ] }" } ], "complaints": [ { "id": 222019111000005678, "mid": 22201909000071469, "campaign": "KiKUU Order Shipped Reminder", "subject": "KiKUU Order Shipped Reminder", "uid": "65655dnvdte0", "email": "one@someone.com", "ip": "117.136.31.250", "location": "中国/广东省/广州市", "longitude": 116.7139, "latitude": 39.5292, "device": "", "platform": "Windows 7", "client": "Chrome", "source": "esp", "content": "在沒有任何解釋及通知的情況下, 退還按金時直接扣了大約27英鎊。在現場交還汽車時並沒有指出任何問題需要作出賠償,現在卻無理地少退還了按金。請協助跟進。", "createdAt": 1392890885000,//2014-02-20 18:08:05 "trackingId": "", "trackingData": "" } ] }
4. Webhook接收消息实体Java代码示例:
-
package org.unimarketing.api.webhook.example; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSONArray; import net.sf.json.JSONNull; import net.sf.json.JSONObject; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.lang.StringUtils; public class ReportDataServlet extends HttpServlet { private static final long serialVersionUID = 4386871944161809516L; private final Logger log = LoggerFactory.getLogger(getClass()); @SuppressWarnings("deprecation") @Override protected void service(HttpServletRequest request, HttpServletResponse response) { try { BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream(), "utf-8")); StringBuffer sb = new StringBuffer(""); String temp; while ((temp = br.readLine()) != null) { sb.append(temp); } br.close(); String result = sb.toString(); if (StringUtils.isBlank(result)) { return; } //缓存报表明细数据 List
reportDataList = new ArrayList (); JSONObject json = JSONObject.fromObject(result); JSONArray deliveryStatusArray = null; if (json.containsKey("delivered")) {//未来将名称改为receipts deliveryStatusArray = JSONArray.fromObject(json.get("delivered")); } else if (json.containsKey("receipts")) { deliveryStatusArray = JSONArray.fromObject(json.get("receipts")); } if (null != deliveryStatusArray) { for (int i = 0; i < deliveryStatusArray.size(); i++) { JSONObject jb = deliveryStatusArray.getJSONObject(i); ReportData reportData = new ReportData(); reportData.setAction("delivered"); reportData.setCtx_envelopeId(jb.getLong("id")); reportData.setEmail(jb.getString("email")); reportData.setStatus(jb.getString("status")); reportData.setDsn(jb.getString("dsn")); reportData.setAction_time(jb.getString("createdAt")); reportData.setCtx_scheduleId(jb.getLong("scheduleId")); reportData.setCtx_contactId(jb.getLong("contactId")); reportData.setCtx_messageId(jb.getLong("messageId")); reportDataList.add(reportData); } } if (json.containsKey("clicks")) { JSONArray clicksArray = JSONArray.fromObject(json.get("clicks")); for (int i = 0; i < clicksArray.size(); i++) { JSONObject jb = clicksArray.getJSONObject(i); ReportData reportData = new ReportData(); reportData.setAction("click"); reportData.setSubject(jb.getString("subject")); reportData.setUid(jb.getString("uid")); reportData.setEmail(jb.getString("email")); reportData.setIp(jb.getString("ip")); reportData.setLocation(jb.getString("location")); if (jb.containsKey("longitude") && !JSONNull.getInstance().equals(jb.get("longitude"))) { reportData.setLon(jb.getDouble("longitude")); } if (jb.containsKey("latitude") && !JSONNull.getInstance().equals(jb.get("latitude"))) { reportData.setLat(jb.getDouble("latitude")); } reportData.setDevice(jb.getString("device")); reportData.setPlatform(jb.getString("platform")); reportData.setClient(jb.getString("client")); reportData.setUrl(jb.getString("url")); reportData.setAction_time(jb.getString("createdAt")); reportDataList.add(reportData); } } if (json.containsKey("opens")) { JSONArray opensArray = JSONArray.fromObject(json.get("opens")); for (int i = 0; i < opensArray.size(); i++) { JSONObject jb = opensArray.getJSONObject(i); ReportData reportData = new ReportData(); reportData.setAction("open"); reportData.setSubject(jb.getString("subject")); reportData.setUid(jb.getString("uid")); reportData.setEmail(jb.getString("email")); reportData.setIp(jb.getString("ip")); reportData.setLocation(jb.getString("location")); if (jb.containsKey("longitude") && !JSONNull.getInstance().equals(jb.get("longitude"))) { reportData.setLon(jb.getDouble("longitude")); } if (jb.containsKey("latitude") && !JSONNull.getInstance().equals(jb.get("latitude"))) { reportData.setLat(jb.getDouble("latitude")); } reportData.setDevice(jb.getString("device")); reportData.setPlatform(jb.getString("platform")); reportData.setClient(jb.getString("client")); reportData.setAction_time(jb.getString("createdAt")); reportDataList.add(reportData); } } //后续处理接收到的报表明细数据 //doSomething(); response.setStatus(200); //这批数据已接收到,不用再推送这些数据 } catch (Exception e) { log.error(e.getMessage(), e); response.setStatus(500); } } } public class ReportData { private String uid = ""; private String email = ""; private String ip = ""; private String location = ""; private Double lon = null; private Double lat = null; private String action; private String action_time = null; private String subject = ""; private String url = ""; private String device = ""; private String platform = ""; private String client = ""; private String status = ""; private String dsn = ""; private long ctx_envelopeId = -1L; private long ctx_contactId = -1L; private long ctx_scheduleId = -1L; private long ctx_messageId = -1L; public String getUid() { return uid; } public void setUid(String uid) { this.uid = uid; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public Double getLon() { return lon; } public void setLon(Double lon) { this.lon = lon; } public Double getLat() { return lat; } public void setLat(Double lat) { this.lat = lat; } public String getAction() { return action; } public void setAction(String action) { this.action = action; } public String getAction_time() { return action_time; } public void setAction_time(String action_time) { this.action_time = action_time; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getDevice() { return device; } public void setDevice(String device) { this.device = device; } public String getPlatform() { return platform; } public void setPlatform(String platform) { this.platform = platform; } public String getClient() { return client; } public void setClient(String client) { this.client = client; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getDsn() { return dsn; } public void setDsn(String dsn) { this.dsn = dsn; } public long getCtx_envelopeId() { return ctx_envelopeId; } public void setCtx_envelopeId(long ctx_envelopeId) { this.ctx_envelopeId = ctx_envelopeId; } public long getCtx_contactId() { return ctx_contactId; } public void setCtx_contactId(long ctx_contactId) { this.ctx_contactId = ctx_contactId; } public long getCtx_scheduleId() { return ctx_scheduleId; } public void setCtx_scheduleId(long ctx_scheduleId) { this.ctx_scheduleId = ctx_scheduleId; } public long getCtx_messageId() { return ctx_messageId; } public void setCtx_messageId(long ctx_messageId) { this.ctx_messageId = ctx_messageId; } }