You are viewing an old version of this page. View the current version.
Compare with Current
View Page History
« Previous
Version 30
Next »
사용자에게 보상을 지급하기 위해서 포스트백(Postback) 연동이 필요합니다. 포스트백 요청은 보안을 위해 서버 간 통신(Server-to-Server) 형태로 진행됩니다.
절차 요약
버즈빌의 포인트 적립 요청을 받을 수 있는 서버 endpoint 구축 (해당 endpoint 의 URL을 postback url
로 지칭)
본 가이드에 따라 Server-to-Server 연동
버즈빌 기술지원 매니저에게 postback url 전달
Index
Introduction
버즈빌을 통해 유저가 포인트를 지급받은 경우, 매체사에게 이 사실을 전달하여 적립 요청을 보내기 위한 API입니다.
| 항목 | 내용 |
---|
1 | 요청 방향 | 버즈빌 → 매체사 |
2 | HTTP Request method | POST - application/x-www-form-urlencoded |
3 | HTTP Request URL | 매체사 서버의 endpoint |
4 | HTTP Request Parameters | 아래 Http Request Parameters 테이블을 참고합니다. |
5 | HTTP Response Code | 버즈빌 서버는 매체사 서버로 부터 전달받은 응답 코드(Response Code)를 바탕으로 성공 여부를 판단합니다. |
HTTP Request Parameters
기능에 따라서 두 종류의 포스트백이 존재합니다. 각 타입에 관한 포스트백 파라미터 정보는 아래의 표를 참고하세요.
파라미터 필드(Field) | 타입 (Type) | 설명 |
---|
user_id
REQUIRED | String (max 255) | 매체사에서 정의한 유저의 식별값 |
transaction_id
REQUIRED | String (max 32) | 보상에 발급되는 ID. 각 보상을 식별하고 포인트 중복 지급을 방지하기 위해 사용
재적립 시도 시 동일한 transaction_id 로 포스트백을 요청합니다. 같은 transaction_id 로 요청이 온 경우에 포인트가 중복 적립되지는 않는지 필수적으로 유의해서 처리해야 합니다.
|
point
REQUIRED | Integer | 사용자에게 지급해야하는 포인트 |
unit_id
REQUIRED | Long | 광고가 노출된 지면의 ID 값 |
title
REQUIRED | String (max 255) | 포인트가 지급된 방식에 설정된 이름 광고 (참여한 광고의 이름) 그 외는 모두 빈값
|
action_type
REQUIRED | String (max 32) | 포인트를 지급 받기 위해 사용자가 취한 액션 타입 opened : Feed 지면 진입 (지면을 방문하기만 해도 기본 리워드 적립)
u : 잠금 해제
l : 랜딩
a : 액션 (해당 광고의 요구 액션을 완료했을 때)
won : Potto에서 당첨번호에 당첨되었을 시 포인트 적립을 요청
manual : 담당자 수기 적립 요청
spinned : 룰렛 미션 참여 시 지급되는 포인트
daily : Feed 출석체크(데일리 리워드 이벤트) 참여 보상
|
event_at
REQUIRED | Long (timestamp) | 포인트 지급 시점 (UNIX Timestamp 초단위)
대부분 API 호출시점과 동일하지만 API 호출이 재시도인 경우 다를 수 있습니다.
|
extra
REQUIRED | String (max 1024) | 파라미터를 추가해야할 경우, 해당 파라미터(JSON serialize 된 문자열 값)를 활용 |
data
OPTIONAL | String | HTTP request parameter를 암호화 해서 전송하는 경우 사용되는 파라미터 |
c
OPTIONAL | String | HTTP request parameter에 Checksum을 전송하는 경우 사용되는 파라미터 |
custom2
OPTIONAL | String (max 255) | 퍼블리셔 실시간 S2S API 제품 연동 매체사에서 지정하는 커스텀 파라미터 |
custom3
OPTIONAL | String (max 255) | 퍼블리셔 실시간 S2S API 제품 연동 매체사에서 지정하는 커스텀 파라미터 |
custom4
OPTIONAL | String (max 255) | 퍼블리셔 실시간 S2S API 제품 연동 매체사에서 지정하는 커스텀 파라미터 |
HTTP Request Parameter 포스트백 예제
다음은 HTTP Request Parameter의 포스트백 예제입니다.
{
"user_id": "12345",
"point": 1,
"transaction_id": "126905422_10000001",
"event_at": 1641452397,
"unit_id": 5539189976900000,
"action_type": "l",
"title": "\uad11\uace0\u0020\ud2b9\uac00",
"extra": "{}"
}
IP Whitelist 추가
버즈빌 서버에서 보내는 포인트 적립 요청을 받을 수 있도록 아래 IP에 대한 inbound 방화벽 예외 처리를 부탁 드립니다.
54.64.39.245
52.68.114.43
13.113.136.11
52.194.132.196
13.114.88.146
요청 파라미터 검증 OPTIONAL
버즈빌 서버에서 전달되는 포인트 적립 요청(포스트백)을 암호화 할 수 있습니다. 이 과정은 필수 적용 항목은 아니며, 필요한 경우 아래의 두 가지 방법을 제공합니다.
1. HTTP Request Parameter Encryption/Decryption
버즈빌 서버에서 매체사로 보내는 HTTP Request parameter를 암호화 하고 싶은 경우 사용합니다. 제공하는 암호화 방식은 고급 암호화 표준, AES(Advanded Encryption Standard)입니다. 제공되는 블록 암호화는 AES-256
이고, 사용하는 블록 암호화 모드(Block Cipher)는 CBC(Cipher Block Chaning) Mode와 PKCS7을 사용합니다.
진행 과정
준비물
진행 절차
버즈빌 서버에서 HTTP Request parameter를 암호화
JSON serialized parameters(string)에 해당 string을 UTF-8
인코딩 적용
해당 string에 PKCS7
패딩 적용 및 AES 암호화 진행
암호화된 값을 base64
encoding을 진행
암호화 된 데이터를 HTTP POST request에 파라미터 data
라는 이름으로 추가하여 전송
e.g
{
"data": "cg087LiIp30jCWpc3MVLfxPL4F05OFGGCkQwwpS6pRVMZhkumzfTFxc8iBoZ8unI15uk0cmY+CbSeOaLHsd7PaxsbyKISiJ31WJJ1OwfaYttoMwFysKNfL7pSz2HB9ULWZicG8MSPxCPKr9RDqgOXpuEoVm9YR3I4yNE5M0LNltpCTdXRBjTrOcjp+RtEZ1VENtHqTICK18nDqO+91BUt3AJsf4VmzogJ8UpA0izEbY="
}
수신 측 (매체사) 에서는 HTTP POST request에서 data
파라미터를 가져와 아래와 같은 순서로 복호화
암호화된 값을 base64
decoding 진행
해당 decoding된 string을 복호화 진행 이후 PKCS7
패딩 제거
해당 string을 UTF-8
디코딩
예제
{
"unit_id":"12345",
"transaction_id":"10000000_1",
"user_id":"buzzvil",
"point": 1,
"action_type":"won",
"event_at": 1599622182,
"title":"title",
"extra": "{}"
}
각 언어별로 제공되는 예시는 아래와 같습니다.
Java 1.8+
import java.lang.RuntimeException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.*;
import javax.crypto.spec.SecretKeySpec;
public class Main {
public static void main(String[] args) {
String aesKey = "BuzzvilAESKeyTest123456789101112";
String aesIV = "0000000000000000";
// Check to make sure encryption is compatible with Buzzvil
String toBuzzvilMessage = "{\"success\": 1, \"reason\": \"중복 적립 요청\"}";
String encrypted = encrypt(toBuzzvilMessage, aesKey, aesIV);
System.out.println("----encrypted----\n"+encrypted);
System.out.println("buzzvil encryption compatible? YES");
// Check to make sure values coming from Buzzvil is decryptable
String fromBuzzvilEncrypted = "IGCdundUBkXf3s7VXl0pqIKDSC/KGc2j8n1DBLKLZAHqkYlG+aWW+G5hGLvoNeUjlI42FtJLpwGUYbFlhy0QXLQv1Z+P7iUOyJrhujmFWX1FdJ5ZBefA5aceGiOlN119NPAX3JOuUAf45HkWG52NcdaHOzWu8rTnghSeLPo9QK0t6l/2gSFvGtOfZolnAHNZAeGEmcqAkhPmUoFtRAW+Zh6TNQY68FrSUI/XYc87Ky0ndaug1Kf7Ogbf8zLK+tJ4LdTCn9A+wcWxEpdkX45f1r/8jTIUK/s1PqBirXFuruq5/XhkhFmdq/I0qBAJ0uxBnk+29GaEQVMtYTzB+eJWTgrQzKhN6Nww2XEPEOl27yH+K0F+sj8QpZ0jkPETadP0gpwKMKv3zlA6xyndIYWrpw==";
String fromBuzzvilDecrypted = "{\"point\": 1, \"user_id\": \"buzzvil_test\", \"transaction_id\": \"100004_100000000\", \"event_at\": 1588936508, \"campaign_name\": \"버즈빌 테스트 campaign_name\", \"extra\": \"{}\", \"action_type\": \"l\", \"base_point\": 1, \"campaign_id\": 202010160022, \"is_media\": 1, \"unit_id\": 452613281179508, \"revenue_type\": \"cpm\"}";
String decrypted = decrypt(fromBuzzvilEncrypted, aesKey, aesIV);
System.out.println("----decrypted-----\n"+decrypted);
System.out.println("buzzvil decryption compatible? "+fromBuzzvilDecrypted.equals(decrypted));
}
static String encrypt(String message, String key, String iv) {
try {
// 1. encode in utf-8
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
// 2. encrypt message through AES CBC with PKCS7
byte[] encrypted = aes(messageBytes, key, iv, Cipher.ENCRYPT_MODE);
// 3. encode in base64
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
System.out.println(e.getMessage());
throw new RuntimeException(e);
}
}
static String decrypt(String message, String key, String iv) {
try {
// 1. decode base64 message
byte[] base64DecodedBytes = Base64.getDecoder().decode(message);
// 2. decrypt message through AES CBC with PKCS7
byte[] decrypted = aes(base64DecodedBytes, key, iv, Cipher.DECRYPT_MODE);
// 3. decode in utf-8
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
System.out.println(e.getMessage());
throw new RuntimeException(e);
}
}
static byte[] aes(byte[] messageBytes, String key, String iv, int cipherMode) {
try {
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
byte[] ivBytes = iv.getBytes(StandardCharsets.UTF_8);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(cipherMode, keySpec, ivSpec);
return cipher.doFinal(messageBytes);
} catch (Exception e) {
System.out.println(e.getMessage());
throw new RuntimeException(e);
}
}
}
PHP 7.0+
<?php
function encrypt($str, $mode, $key, $iv) {
$encrypted = openssl_encrypt($str, $mode, $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($encrypted);
}
function decrypt($str, $mode, $key, $iv) {
$text = base64_decode($str);
return openssl_decrypt($text, $mode, $key, OPENSSL_RAW_DATA, $iv);
}
$mode = "AES-256-CBC";
$key = "BuzzvilAESKeyTest123456789101112";
$iv = "0000000000000000";
$plaintext = '{"success": 1, "reason": "중복 적립 요청"}';
$encrypted = encrypt($plaintext, $mode, $key, $iv);
print ("ENCRYPTED: ".$encrypted."\n");
// +VEmHrt+jwI6Dg2zImdGtI+iIQEqV8v5btpS1a3cdEQBzIc72V9aKju5m6+ELTBixbITMBoHIYjj8jJbsKbIgg==
$fromBuzzvil = "IGCdundUBkXf3s7VXl0pqIKDSC/KGc2j8n1DBLKLZAHqkYlG+aWW+G5hGLvoNeUjlI42FtJLpwGUYbFlhy0QXLQv1Z+P7iUOyJrhujmFWX1FdJ5ZBefA5aceGiOlN119NPAX3JOuUAf45HkWG52NcdaHOzWu8rTnghSeLPo9QK0t6l/2gSFvGtOfZolnAHNZAeGEmcqAkhPmUoFtRAW+Zh6TNQY68FrSUI/XYc87Ky0ndaug1Kf7Ogbf8zLK+tJ4LdTCn9A+wcWxEpdkX45f1r/8jTIUK/s1PqBirXFuruq5/XhkhFmdq/I0qBAJ0uxBnk+29GaEQVMtYTzB+eJWTgrQzKhN6Nww2XEPEOl27yH+K0F+sj8QpZ0jkPETadP0gpwKMKv3zlA6xyndIYWrpw==";
$decrypted = decrypt($fromBuzzvil, $mode, $key, $iv);
print("DECRYPTED: ".$decrypted);
/*
{"point": 1, "user_id": "buzzvil_test", "transaction_id": "100004_100000000", "event_at": 1588936508, "campaign_name": "버즈빌 테스트 campaign_name", "extra": "{}", "action_type": "l", "base_point": 1, "campaign_id": 202010160022, "is_media": 1, "unit_id": 452613281179508, "revenue_type": "cpm"}
*/
?>
Python 2.7+ & Python 3.6+
Python 2.7+
# -*- coding:utf-8 -*-
import base64
# https://pypi.python.org/pypi/pycryptodome/3.9.9
from Crypto.Cipher import AES
# https://pypi.python.org/pypi/pkcs7/0.1.2
from pkcs7 import PKCS7Encoder
def encrypt(message, key, iv):
message_plaintext_padded = PKCS7Encoder().encode(message)
cipher = AES.new(key, AES.MODE_CBC, iv)
message_encrypted_raw = cipher.encrypt(message_plaintext_padded)
return base64.b64encode(message_encrypted_raw)
def decrypt(message, key, iv):
message_decoded = base64.b64decode(message)
cipher = AES.new(key, AES.MODE_CBC, iv)
messaged_decrypted_padded = cipher.decrypt(message_decoded)
message_plaintext_decoded = PKCS7Encoder().decode(messaged_decrypted_padded)
return message_plaintext_decoded
iv = '0000000000000000'
key = 'BuzzvilAESKeyTest123456789101112'
plaintext = '{"success": 1, "reason": "중복 적립 요청"}'
encrypted = encrypt(plaintext, key, iv)
print "ENCRYPTED: {}".format(encrypted)
# +VEmHrt+jwI6Dg2zImdGtI+iIQEqV8v5btpS1a3cdEQBzIc72V9aKju5m6+ELTBixbITMBoHIYjj8jJbsKbIgg==
from_buzzvil = "IGCdundUBkXf3s7VXl0pqIKDSC/KGc2j8n1DBLKLZAHqkYlG+aWW+G5hGLvoNeUjlI42FtJLpwGUYbFlhy0QXLQv1Z+P7iUOyJrhujmFWX1FdJ5ZBefA5aceGiOlN119NPAX3JOuUAf45HkWG52NcdaHOzWu8rTnghSeLPo9QK0t6l/2gSFvGtOfZolnAHNZAeGEmcqAkhPmUoFtRAW+Zh6TNQY68FrSUI/XYc87Ky0ndaug1Kf7Ogbf8zLK+tJ4LdTCn9A+wcWxEpdkX45f1r/8jTIUK/s1PqBirXFuruq5/XhkhFmdq/I0qBAJ0uxBnk+29GaEQVMtYTzB+eJWTgrQzKhN6Nww2XEPEOl27yH+K0F+sj8QpZ0jkPETadP0gpwKMKv3zlA6xyndIYWrpw=="
decrypted = decrypt(from_buzzvil, key, iv)
print "DECRYPTED: {}".format(decrypted)
#{"point": 1, "user_id": "buzzvil_test", "transaction_id": "100004_100000000", "event_at": 1588936508, "campaign_name": "버즈빌 테스트 campaign_name", "extra": "{}", "action_type": "l", "base_point": 1, "campaign_id": 202010160022, "is_media": 1, "unit_id": 452613281179508, "revenue_type": "cpm"}
Python 3.6+
# -*- coding:utf-8 -*-
import base64
# https://pypi.python.org/pypi/pycryptodome/3.9.9
from Crypto.Cipher import AES
from binascii import unhexlify
BLOCK_SIZE = 16
def encrypt(message, key, iv):
message_plaintext_padded = pad(message).encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, iv)
message_encrypted_raw = cipher.encrypt(message_plaintext_padded)
return base64.b64encode(message_encrypted_raw)
def decrypt(message, key, iv):
message_decoded = base64.b64decode(message)
cipher = AES.new(key, AES.MODE_CBC, iv)
messaged_decrypted_padded = unpad(cipher.decrypt(message_decoded))
return messaged_decrypted_padded.decode('utf-8')
def pad(s):
return s + (BLOCK_SIZE - len(s.encode('utf-8')) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s.encode('utf-8')) % BLOCK_SIZE)
def unpad(s):
return s[:-ord(s[len(s)-1:])]
iv = '0000000000000000'.encode('utf-8')
key = 'BuzzvilAESKeyTest123456789101112'.encode('utf-8')
plaintext = '{"success": 1, "reason": "중복 적립 요청"}'
encrypted = encrypt(plaintext, key, iv)
print ("ENCRYPTED: {}".format(encrypted))
# +VEmHrt+jwI6Dg2zImdGtI+iIQEqV8v5btpS1a3cdEQBzIc72V9aKju5m6+ELTBixbITMBoHIYjj8jJbsKbIgg==
from_buzzvil = "IGCdundUBkXf3s7VXl0pqIKDSC/KGc2j8n1DBLKLZAHqkYlG+aWW+G5hGLvoNeUjlI42FtJLpwGUYbFlhy0QXLQv1Z+P7iUOyJrhujmFWX1FdJ5ZBefA5aceGiOlN119NPAX3JOuUAf45HkWG52NcdaHOzWu8rTnghSeLPo9QK0t6l/2gSFvGtOfZolnAHNZAeGEmcqAkhPmUoFtRAW+Zh6TNQY68FrSUI/XYc87Ky0ndaug1Kf7Ogbf8zLK+tJ4LdTCn9A+wcWxEpdkX45f1r/8jTIUK/s1PqBirXFuruq5/XhkhFmdq/I0qBAJ0uxBnk+29GaEQVMtYTzB+eJWTgrQzKhN6Nww2XEPEOl27yH+K0F+sj8QpZ0jkPETadP0gpwKMKv3zlA6xyndIYWrpw=="
decrypted = decrypt(from_buzzvil, key, iv)
print ("DECRYPTED: {}".format(decrypted))
#{"point": 1, "user_id": "buzzvil_test", "transaction_id": "100004_100000000", "event_at": 1588936508, "campaign_name": "버즈빌 테스트 campaign_name", "extra": "{}", "action_type": "l", "base_point": 1, "campaign_id": 202010160022, "is_media": 1, "unit_id": 452613281179508, "revenue_type": "cpm"}
2. Add Checksum Parameter
포스트백 데이터 검증을 위해 Request Parameter에 Checksum parameter를 추가하고자 하는 경우 사용합니다. 제공하는 데이터 검증 방식은 HMAC 인증이고, SHA-256
알고리즘을 사용합니다.
진행 과정
준비물
진행 절차
전달받은 포스트백 파라미터 중 transaction_id
, user_id
, point
, event_at
값으로 아래 형식대로 String을 생성 (이하 "msg"
)
주의사항 : msg
값은 인코딩되어야 하며, 각 파라미터 string 과 colon 사이에는 띄어쓰기가 없습니다.
msg=u'{0}:{1}:{2}:{3}'.format(
params['transaction_id'],
params['user_id'],
params['point'],
params['event_at'],
).encode('utf-8')
HMAC SHA-256 알고리즘을 사용하여 msg
값을 암호화
key: 1번에서 발급받은 hmac_key
data: 2번에서 생성한 msg
전달받은 포스트백 파라미터 중 c
필드의 값과 비교, 일치 여부를 확인
예제