웹훅 엔드포인트 생성
POST/v1/endpoints
계약 v1 / 1.0.0 62 / 2026-10-10
이 페이지에서
프로젝트 HTTPS 수신자를 추가하세요. 계정당 최대 20개의 활성 엔드포인트를 허용합니다. 같은 API 키로 같은 프로젝트의 동일 URL을 다시 등록해도 전달이 중복되지 않습니다. 200은 기존 ID를 반환하며 비밀 값은 없습니다. 201 후 비밀 값을 저장하세요. 한도에 도달하면 재등록도 402을 반환할 수 있습니다.
키 권한: webhooks / write.
요청 본문
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
url | string | 예 | 공개 HTTPS 웹훅 수신 URL입니다. 로컬 및 비공개 주소는 거부됩니다.uri; pattern: ^https:// |
label | string | 아니요 | 선택적 대상 이름입니다. |
project | string | 아니요 | 프로젝트 ID입니다. 생략하면 키의 프로젝트 또는 계정의 기본 프로젝트를 사용합니다. |
application/json
{
"project": "1002",
"url": "https://merchant.example/webhooks/cryptanio",
"label": "Orders"
}요청 예시
예제는 귀하의 계정에서 실제 작업을 수행합니다. 코드에 명시된 환경 변수를 설정하세요. 키를 비공개로 유지하세요. 쓰기 요청은 자동 재시도하지 않습니다.
Shell / cURL
#!/usr/bin/env bash
set -euo pipefail
: "${CRYPTANIO_API_KEY:?Set CRYPTANIO_API_KEY}"
: "${CRYPTANIO_PROJECT_ID:?Set CRYPTANIO_PROJECT_ID}"
json_string() {
local text="$1" char code index output='"'
for ((index=0; index<${#text}; index++)); do
char="${text:index:1}"
case "$char" in
'"'|'\') output+="\\$char" ;;
*) printf -v code '%d' "'$char"
if ((code < 32)); then printf -v char '\\u%04x' "$code"; fi
output+="$char" ;;
esac
done
printf '%s"' "$output"
}
response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
transfer_status=0
http_status="$(curl \
--silent --show-error --max-time 30 --request POST \
--header "Authorization: Bearer $CRYPTANIO_API_KEY" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data-binary '{"project":'"$(json_string "$CRYPTANIO_PROJECT_ID")"',"url":"https://merchant.example/webhooks/cryptanio","label":"Orders"}' \
"https://api.cryptanio.com/v1/endpoints" \
--output "$response_file" --write-out '%{http_code}')" || transfer_status=$?
if [[ "$http_status" == 2?? ]]; then
cat "$response_file"
exit "$transfer_status"
fi
printf 'HTTP %s\n' "$http_status" >&2
cat "$response_file" >&2
exit 1
Go
// Go; standard library only. Run: go run request.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
func env(name string) string {
value := os.Getenv(name)
if value == "" { fmt.Fprintln(os.Stderr, "Set", name); os.Exit(1) }
return value
}
func run() error {
path := "/v1/endpoints"
endpoint, err := url.Parse("https://api.cryptanio.com" + path)
if err != nil { return err }
query := endpoint.Query()
endpoint.RawQuery = query.Encode()
var payload map[string]any
if err := json.Unmarshal([]byte("{\"project\":\"1002\",\"url\":\"https://merchant.example/webhooks/cryptanio\",\"label\":\"Orders\"}"), &payload); err != nil { return err }
payload["project"] = env("CRYPTANIO_PROJECT_ID")
body, err := json.Marshal(payload)
if err != nil { return err }
request, err := http.NewRequest("POST", endpoint.String(), bytes.NewReader(body))
if err != nil { return err }
request.Header.Set("Authorization", "Bearer " + env("CRYPTANIO_API_KEY"))
request.Header.Set("Accept", "application/json")
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
response, err := client.Do(request)
if err != nil { return err }
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 {
body, err := io.ReadAll(response.Body)
if err != nil { return err }
return fmt.Errorf("HTTP %d: %s", response.StatusCode, body)
}
_, err = io.Copy(os.Stdout, response.Body)
return err
}
func main() {
if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) }
}
Rust
// Cargo.toml dependencies:
// reqwest = { version = "=0.12.24", features = ["blocking", "json"] }
// serde_json = "=1.0.145"
use std::{error::Error, io, time::Duration};
use reqwest::{blocking::Client, redirect::Policy, Method, Url};
fn env(name: &str) -> Result<String, Box<dyn Error>> {
let value = std::env::var(name)?;
if value.is_empty() { return Err(format!("Set {name}").into()); }
Ok(value)
}
fn run() -> Result<(), Box<dyn Error>> {
let mut url = Url::parse("https://api.cryptanio.com")?;
{
let mut segments = url.path_segments_mut().map_err(|_| "Invalid base URL")?;
segments.clear();
segments.push("v1");
segments.push("endpoints");
}
let client = Client::builder().timeout(Duration::from_secs(30)).redirect(Policy::none()).build()?;
let request = client.request(Method::POST, url)
.header("Authorization", format!("Bearer {}", env("CRYPTANIO_API_KEY")?))
.header("Accept", "application/json".to_owned())
.header("Content-Type", "application/json".to_owned());
let mut body: serde_json::Value = serde_json::from_str("{\"project\":\"1002\",\"url\":\"https://merchant.example/webhooks/cryptanio\",\"label\":\"Orders\"}")?;
body["project"] = serde_json::Value::String(env("CRYPTANIO_PROJECT_ID")?);
let request = request.json(&body);
let mut response = request.send()?;
let status = response.status();
if !status.is_success() { return Err(format!("HTTP {}: {}", status.as_u16(), response.text()?).into()); }
io::copy(&mut response, &mut io::stdout())?;
Ok(())
}
fn main() {
if let Err(error) = run() { eprintln!("{error}"); std::process::exit(1); }
}
PHP
<?php
// PHP 8 with the cURL extension. Run this on your server.
function env(string $name): string {
$value = getenv($name);
if ($value === false || $value === '') throw new RuntimeException('Set ' . $name);
return $value;
}
try {
$path = '/v1/endpoints';
$query = [
];
$url = 'https://api.cryptanio.com' . $path . ($query ? '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986) : '');
$headers = [
'Authorization: ' . 'Bearer ' . env('CRYPTANIO_API_KEY'),
'Accept: ' . 'application/json',
'Content-Type: ' . 'application/json',
];
$body = json_decode('{"project":"1002","url":"https://merchant.example/webhooks/cryptanio","label":"Orders"}', true, 512, JSON_THROW_ON_ERROR);
$body['project'] = env('CRYPTANIO_PROJECT_ID');
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_POSTFIELDS => json_encode($body, JSON_THROW_ON_ERROR),
]);
$response = curl_exec($curl);
if ($response === false) throw new RuntimeException(curl_error($curl));
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($status < 200 || $status >= 300) throw new RuntimeException('HTTP ' . $status . ': ' . $response);
echo $response;
} catch (Throwable $error) {
fwrite(STDERR, $error->getMessage() . PHP_EOL);
exit(1);
}
Python
# Python 3; standard library only.
import json
import os
import sys
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlencode
from urllib.request import HTTPRedirectHandler, Request, build_opener
def env(name):
result = os.environ.get(name)
if not result:
raise ValueError("Set " + name)
return result
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
try:
path = "/v1/endpoints"
query = {
}
url = "https://api.cryptanio.com" + path + ("?" + urlencode(query) if query else "")
headers = {
"Authorization": "Bearer " + env("CRYPTANIO_API_KEY"),
"Accept": "application/json",
"Content-Type": "application/json",
}
body = json.loads("{\"project\":\"1002\",\"url\":\"https://merchant.example/webhooks/cryptanio\",\"label\":\"Orders\"}")
body["project"] = env("CRYPTANIO_PROJECT_ID")
data = json.dumps(body).encode("utf-8")
request = Request(url, data=data, headers=headers, method="POST")
with build_opener(NoRedirect()).open(request, timeout=30) as response:
if not 200 <= response.status < 300:
raise ValueError("HTTP " + str(response.status))
sys.stdout.buffer.write(response.read())
except HTTPError as error:
print("HTTP " + str(error.code) + ": " + error.read().decode("utf-8", errors="replace"), file=sys.stderr)
sys.exit(1)
except (ValueError, URLError, OSError) as error:
print(str(error), file=sys.stderr)
sys.exit(1)
Node.js
// Node.js 18+. Run on your server, never in a browser.
const env = name => {
const result = process.env[name];
if (!result) throw new Error(`Set ${name}`);
return result;
};
try {
let path = "/v1/endpoints";
const url = new URL(path, "https://api.cryptanio.com");
const headers = {
"Authorization": "Bearer " + env("CRYPTANIO_API_KEY"),
"Accept": "application/json",
"Content-Type": "application/json",
};
const body = {
"project": "1002",
"url": "https://merchant.example/webhooks/cryptanio",
"label": "Orders"
};
body["project"] = env("CRYPTANIO_PROJECT_ID");
const response = await fetch(url, {
method: "POST", headers, redirect: "manual",
signal: AbortSignal.timeout(30_000), body: JSON.stringify(body),
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
if (response.body) {
for await (const chunk of response.body) process.stdout.write(chunk);
}
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
응답 및 오류
200 이미 등록된 엔드포인트이며 비밀 값은 제외됩니다.
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
id | string | 예 | 불투명 식별자입니다. 문자열로 유지하세요. minLength: 1 |
project_id | string | 예 | 불투명 식별자입니다. 문자열로 유지하세요. minLength: 1 |
sandbox | boolean | 예 | 테스트 대상 표시입니다. 공개 고객 샌드박스 제공을 의미하지 않습니다. |
url | string | 예 | 대상 HTTPS URL입니다.uri |
label | string | 아니요 | 라벨입니다. |
secret | string | 아니요 | 서명 비밀 값입니다. 새로 생성할 때만 반환하며 목록이나 반복 등록에는 반환하지 않습니다. |
source | string | 예 | api, dashboard, browser 등 대상 생성 방식입니다. 새로운 값이 추가될 수 있습니다. |
created_at | string | 예 | RFC 3339 형식의 UTC 날짜 및 시간입니다.date-time |
200 application/json
{
"id": "2200",
"project_id": "1002",
"sandbox": true,
"url": "https://merchant.example/webhooks/cryptanio",
"source": "api",
"created_at": "2026-10-06T10:00:00Z"
}201 성공 응답.
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
id | string | 예 | 불투명 식별자입니다. 문자열로 유지하세요. minLength: 1 |
project_id | string | 예 | 불투명 식별자입니다. 문자열로 유지하세요. minLength: 1 |
sandbox | boolean | 예 | 테스트 대상 표시입니다. 공개 고객 샌드박스 제공을 의미하지 않습니다. |
url | string | 예 | 대상 HTTPS URL입니다.uri |
label | string | 아니요 | 라벨입니다. |
secret | string | 아니요 | 서명 비밀 값입니다. 새로 생성할 때만 반환하며 목록이나 반복 등록에는 반환하지 않습니다. |
source | string | 예 | api, dashboard, browser 등 대상 생성 방식입니다. 새로운 값이 추가될 수 있습니다. |
created_at | string | 예 | RFC 3339 형식의 UTC 날짜 및 시간입니다.date-time |
201 application/json
{
"id": "2200",
"project_id": "1002",
"sandbox": true,
"url": "https://merchant.example/webhooks/cryptanio",
"source": "api",
"created_at": "2026-10-06T10:00:00Z"
}400 잘못된 요청(bad_request, unsupported_asset).
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
error | object | 예 |
400 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}401 유효한 활성 API 키가 없습니다(unauthorized).
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
error | object | 예 |
401 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}402 리소스 한도에 도달했습니다(limit_reached).
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
error | object | 예 |
402 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}403 키가 작업, 프로젝트, 서비스 또는 IP를 허용하지 않거나 이메일이 확인되지 않았습니다.
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
error | object | 예 |
403 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}409 상태 충돌, 재시도 매개변수 충돌 또는 결제 리소스 한도(conflict, limit_reached, service_inactive).
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
error | object | 예 |
409 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}429 요청이 너무 많습니다(rate_limited). Retry-After 이후 재시도하세요.
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
error | object | 예 |
429 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}500 내부 오류(internal).
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
error | object | 예 |
500 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}503 일시적 사용 불가(unavailable). 점진적 대기로 재시도하세요.
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
error | object | 예 |
503 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}중첩 객체
Endpoint
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
id | string | 예 | 불투명 식별자입니다. 문자열로 유지하세요. minLength: 1 |
project_id | string | 예 | 불투명 식별자입니다. 문자열로 유지하세요. minLength: 1 |
sandbox | boolean | 예 | 테스트 대상 표시입니다. 공개 고객 샌드박스 제공을 의미하지 않습니다. |
url | string | 예 | 대상 HTTPS URL입니다.uri |
label | string | 아니요 | 라벨입니다. |
secret | string | 아니요 | 서명 비밀 값입니다. 새로 생성할 때만 반환하며 목록이나 반복 등록에는 반환하지 않습니다. |
source | string | 예 | api, dashboard, browser 등 대상 생성 방식입니다. 새로운 값이 추가될 수 있습니다. |
created_at | string | 예 | RFC 3339 형식의 UTC 날짜 및 시간입니다.date-time |
ID
불투명 식별자입니다. 형식은 보장되지 않으므로 숫자로 변환하지 마세요.
string 불투명 식별자입니다. 형식은 보장되지 않으며 숫자로 변환하지 마세요.
EndpointRequest
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
url | string | 예 | 공개 HTTPS 웹훅 수신 URL입니다. 로컬 및 비공개 주소는 거부됩니다.uri; pattern: ^https:// |
label | string | 아니요 | 선택적 대상 이름입니다. |
project | string | 아니요 | 프로젝트 ID입니다. 생략하면 키의 프로젝트 또는 계정의 기본 프로젝트를 사용합니다. |
Error
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
error | object | 예 |