https://app.gutline.eu/api/v1
海关申报 API 提供申报管理功能,包括创建、确认和取消申报。
重要说明:申报一旦提交到海关系统就不能修改,如需修改请重新创建申报。
认证
参考 Bearer token 认证方式
幂等请求
API 支持幂等请求,您可以安全的重试请求,而不会意外的执行两次相同操作,要执行一个幂等请求须在请求头中增加:
Idempotency-Key: <key> 重试时保证 key 不变
幂等请求仅支持使用 POST 方式的请求,其他方式的请求将被忽略
分页
分页按照 rfc5988 标准
import requests
response = requests.get('https://app-sandbox.gutline.eu/api/v1/declarations', headers={'Authorization': 'Bearer token'})
next_page_url = response.links['next']['url']
response = requests.get(next_page_url, headers={'Authorization': 'Bearer token'})
请求限制
X-RateLimit-Limit每小时请求次数限制X-RateLimit-Remaining剩余请求次数X-RateLimit-Reset下次重置时间 UTC
Webhook 通知
当申报状态发生变化时,我们会向您配置的 webhook 端点发送 HTTP POST 请求。
事件类型
| 事件类型 | 触发时机 | 描述 |
|---|---|---|
declaration.updated |
申报状态更新 | 当申报状态发生变化时触发 |
请求格式
HTTP Headers
Content-Type: application/json
X-Webhook-Event: declaration.updated
X-Webhook-Signature: sha256=abc123...
X-Webhook-Delivery: jid-123456
User-Agent: Gutline-Hookshot/0.1
注意:签名格式为 算法=签名值,如 sha256=abc123...,其中 sha256 是算法,abc123... 是签名值。
请求体结构
{
"event": "declaration.updated",
"payload": {
"object": "declaration",
"id": 222537,
"reference": "DECL_20251020_021323_65558849",
"template_code": "NLAMS_H7",
"client_ref": "NLH7-1727866234",
"lrn": "NLH7-1727866234",
"mrn": "25NLNG746OHK9FBER0",
"declaration_type": "IM",
"additional_declaration_type": "A",
"declaration_office_code": "NL000567",
"presentation_office_code": null,
"shipment_carrier_ref": null,
"mode_at_border": "air",
"inland_mode": null,
"country_of_dispatch_code": "CN",
"country_of_destination_code": "NL",
"gross_weight_in_kg": "0.5",
"total_packages": 10,
"unique_consignment_reference": null,
"location_of_goods_code": null,
"location_of_goods_type": null,
"delivery_terms": null,
"delivery_terms_location": null,
"currency_code": "EUR",
"currency_exchange_rate": null,
"ioss_number": "IM9000000001",
"deferred_payment_account": null,
"status": "released",
"reject_reason": null,
"provisional_validation_datetime": "2025-10-01 12:00:00",
"total_intrinsic_value": "24.0",
"total_freight_cost": "0.0",
"total_insurance_cost": "0.0",
"total_invoice_value": "24.0",
"cancellation_requested_at": null,
"cancelled_at": null,
"submitted_at": null,
"rejected_at": null,
"accepted_at": "2025-10-20T02:13:47.278Z",
"inspected_at": null,
"released_at": "2025-10-20T02:13:47.325Z",
"archived_at": null,
"goods_presentation_effective_date": "2025-10-20",
"goods_presentation_due_date": "2025-11-19",
"importer": {
"eori_number": "DE123456789",
"name": "Import Company B.V.",
"attention_to": "Hans Michaël",
"street_address": "Beëk Street 1",
"city": "Rotterdam",
"postal_code": "3000 AA",
"state": null,
"country_code": "NL",
"email": "hans@test.com",
"phone": "+49 30 123456",
"vat_number": null
},
"exporter": {
"eori_number": null,
"name": "Shanghai Exports Ltd",
"attention_to": null,
"street_address": "Seller Street 1",
"city": "Shanghai",
"postal_code": "200000",
"state": null,
"country_code": "CN",
"email": null,
"phone": null
},
"items": [
{
"id": 221436,
"description": "T-shirts",
"commodity_code": "6109100010",
"national_additional_codes": [],
"additional_reference_codes": [],
"requested_procedure": null,
"additional_procedure_codes": [
"F48",
"C07"
],
"currency_code": "EUR",
"gross_weight_in_grams": 500,
"net_weight_in_grams": 450,
"package_type": null,
"number_of_packages": 1,
"shipping_marks": null,
"quantity": 2,
"supplementary_units": null,
"supplementary_units_amount": null,
"country_of_origin": "CN",
"dangerous_goods_code": null,
"ecommerce_ref": null,
"ecommerce_url": null,
"created_at": "2025-10-20T02:13:23.425Z",
"updated_at": "2025-10-20T02:13:23.425Z",
"intrinsic_value": "24.0",
"freight_cost": null,
"insurance_cost": null,
"customs_value": "24.0",
"documents": [],
"tax_lines": [
{
"id": 97,
"type_code": "B00",
"category": "vat",
"regime_code": null,
"rate": "0.0",
"currency": "EUR",
"base_amount": "24.0",
"deduct_amount": "0.0",
"amount": "0.0"
}
]
}
],
"documents": [
{
"id": 478104,
"category": "supporting",
"type_code": "N380",
"reference_number": "INV-SH-2025-001",
"valid_from": "2025-10-01T00:00:00.000Z",
"file_url": null,
"type_description": "Commercial invoice"
},
{
"id": 478105,
"category": "transport",
"type_code": "N740",
"reference_number": "WBL12345678",
"valid_from": "2025-10-01T00:00:00.000Z",
"file_url": null,
"type_description": "Air Waybill"
}
],
"tax_lines": [
{
"id": 98,
"type_code": "B00",
"category": "vat",
"regime_code": null,
"rate": "0.0",
"currency": "EUR",
"base_amount": "24.0",
"deduct_amount": "0.0",
"amount": "0.0"
}
],
"created_at": "2025-10-20T02:13:23.408Z",
"updated_at": "2025-10-20T02:13:47.354Z",
"url": "https://app-sandbox.gutline.eu/api/v1/declarations/NLH7-1727866234"
}
}
安全验证
我们使用 HMAC 算法对 webhook payload 进行签名验证,支持多种哈希算法(如 SHA256)。
设计理念:使用 算法=签名值 的格式,使得我们可以在后期升级到更安全的算法时,所有客户端都能自动适配,无需修改代码。这确保了系统的向前兼容性和安全性。
# Ruby 示例
def verify_webhook_signature(payload, signature, secret)
algorithm, signature_value = signature.split('=', 2)
expected_signature = OpenSSL::HMAC.hexdigest(algorithm, secret, payload)
Rack::Utils.secure_compare(signature_value, expected_signature)
end
# 使用示例 - 注意 payload 是 raw body
def handle_webhook
payload = request.body.read # 获取 raw body
signature = request.headers['X-Webhook-Signature']
secret = ENV['WEBHOOK_SECRET']
unless verify_webhook_signature(payload, signature, secret)
return head :unauthorized
end
# 解析 JSON
event_data = JSON.parse(payload)
# 处理事件
begin
process_webhook_event(event_data)
head :ok # 处理成功返回 200
rescue => e
Rails.logger.error "Webhook processing failed: #{e.message}"
head :internal_server_error # 处理失败返回 500,触发重试
end
end
# Python 示例
import hmac
import hashlib
import json
def verify_webhook_signature(payload, signature, secret):
algorithm, signature_value = signature.split('=', 1)
expected_signature = hmac.new(
secret.encode(), payload.encode(), getattr(hashlib, algorithm)
).hexdigest()
return hmac.compare_digest(signature_value, expected_signature)
# 使用示例 - 注意 payload 是 raw body
@app.route('/webhooks', methods=['POST'])
def handle_webhook():
payload = request.get_data() # 获取 raw body
signature = request.headers.get('X-Webhook-Signature')
secret = os.environ.get('WEBHOOK_SECRET')
if not verify_webhook_signature(payload, signature, secret):
return 'Unauthorized', 401
try:
# 解析 JSON
event_data = json.loads(payload)
# 处理事件
process_webhook_event(event_data)
return 'OK', 200 # 处理成功返回 200
except Exception as e:
print(f"Webhook processing failed: {e}")
return 'Internal Server Error', 500 # 处理失败返回 500,触发重试
// Node.js 示例
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const [algorithm, signatureValue] = signature.split('=');
const expectedSignature = crypto
.createHmac(algorithm, secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signatureValue),
Buffer.from(expectedSignature)
);
}
// 使用示例 - 注意 payload 是 raw body
app.post('/webhooks', (req, res) => {
const payload = JSON.stringify(req.body); // 获取 raw body
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).send('Unauthorized');
}
try {
// 解析 JSON
const eventData = JSON.parse(payload);
// 处理事件
processWebhookEvent(eventData);
res.status(200).send('OK'); // 处理成功返回 200
} catch (error) {
console.error('Webhook processing failed:', error);
res.status(500).send('Internal Server Error'); // 处理失败返回 500,触发重试
}
});
重试机制
- 最多重试 5 次:如果您的端点返回非 2xx 状态码,我们会重试
- 重试间隔:120 秒
- 失败处理:连续失败 50 次后,webhook 会被暂停
- 自动禁用:暂停 3 天后,webhook 会被自动禁用
- 建议:为了确保事件不丢失,建议在事件处理完全成功后再返回 200 状态码
- 数据更新:每次重试都会发送最新的数据,状态可能已经发生变化
- 设计理念:避免存储大量过期数据,过期数据没有意义,建议使用时间戳判断状态变化
响应要求
- 状态码:必须返回 2xx 状态码表示成功
- 响应时间:建议在 30 秒内响应
- 响应格式:建议返回 JSON 格式响应
- 重要提醒:建议确保事件处理完全成功后再返回 200 状态码,以避免事件丢失
事件状态说明
申报状态流转
pending → submitted → acknowledged → accepted → released
状态流转图:
正常申报流程:
pending ──→ submitted ──→ accepted ──→ released
│
▼
rejected
预申报流程:
pending ──→ submitted ──→ acknowledged ──→ accepted ──→ released
│
▼
rejected
说明:
- 申报从
pending状态开始 acknowledged状态仅用于预申报- 正常申报流程:
pending → submitted → accepted → released - 预申报流程:
pending → submitted → acknowledged → accepted → released - 任何状态都可能被
rejected或cancelled
异常状态
submitted → rejected (海关拒绝)
submitted → cancelled (取消)
重要提醒
重试时数据会更新:每次重试都会发送最新的数据,状态可能已经发生变化。例如:
- 第一次通知:
status: "accepted" - 重试通知:
status: "released"
建议使用时间戳判断:不要根据状态字段判断,而是根据对应的时间戳字段(如 accepted_at, released_at)来判断是否发生了新的状态变化。这些时间戳字段只会设置一次,不会更新,能准确判断是否发生了新的状态变化。这种设计避免了存储大量过期数据,过期数据也没有意义。
常见问题
Q: 重试时数据会变化吗?
A: 是的,每次重试都会发送最新的数据。如果第一次通知时状态是 accepted,重试时可能已经是 released。这是正常的设计,确保接收方总是获得最新状态。
Q: 如何处理状态变化?
A: 建议使用关键时间戳字段来判断是否发生了新的状态变化。主要关注以下字段:
关键时间戳字段:
submitted_at- 提交时间accepted_at- 接受时间inspected_at- 查验时间released_at- 放行时间cancelled_at- 取消时间rejected_at- 拒绝时间
关键业务字段:
mrn- 海关参考号reject_reason- 拒绝原因
处理示例:
# 检查是否被接受
if payload['accepted_at'] && !last_processed_accepted_at
# 处理接受逻辑
last_processed_accepted_at = payload['accepted_at']
end
# 检查是否被放行
if payload['released_at'] && !last_processed_released_at
# 处理放行逻辑
last_processed_released_at = payload['released_at']
end
Q: 为什么使用时间戳而不是状态?
A: 这种设计有两个重要优势:
- 避免存储压力:不需要存储大量过期数据
- 过期数据无意义:过期数据对业务没有价值
- 精确判断:时间戳字段(如
accepted_at、released_at)只会设置一次,能准确判断是否发生了新的状态变化
Q: 如何处理大量事件?
A: 建议异步处理事件,快速返回 2xx 状态码。
Q: 如何获取 webhook 密钥?
A: 在创建 webhook 时,系统会自动生成密钥,请妥善保存。
Q: 如何测试 webhook 配置?
A: 使用 Dashboard 中的 ping 功能发送测试请求。
Q: 为什么使用 算法=签名值 的格式?
A: 这种格式允许我们在后端升级到更安全的算法时,所有客户端都能自动适配,无需修改代码。这确保了系统的向前兼容性和安全性。
Q: 支持哪些签名算法?
A: 我们目前使用 SHA256 算法生成签名。随着时间推移和算力提升,我们会主动升级到更安全的算法(如 SHA384、SHA512 等)。您的代码应支持动态解析签名头中的算法名称,以便在我们升级算法时无需修改代码即可兼容。
Q: 为什么建议处理完成后才返回 200?
A: 返回 200 状态码表示处理成功,我们不会重试该事件。为了确保事件不丢失,建议在业务逻辑完全处理成功后再返回 200 状态码。
Q: payload 是原始字符串还是解析后的对象?
A: payload 是原始的 JSON 字符串(raw body),建议先解析为对象再使用。签名验证需要使用原始字符串。
荷兰海关申报指南
| 实体 | 字段 | 类型 | DECO (F48) | DECO (F49) | DMS | 描述 |
|---|---|---|---|---|---|---|
| Declaration | declaration_type | string | ✓ | ✓ | ✓ | IM |
| Declaration | additional_declaration_type | string | D | D | A | A: 标准申报 D: 预申报 |
| Declaration | declaration_office_code | string | ✓ | ✓ | ✓ | NL000567 |
| Declaration | country_of_dispatch_code | string | ✓ | ✓ | ✓ | CN |
| Declaration | country_of_destination_code | string | ✓ | ✓ | ✓ | NL |
| Declaration | mode_at_border | string | x | x | ✓ | sea,rail,road,air |
| Declaration | transaction_nature_code | string | x | x | ✓ | consumer_sale (默认) |
| Declaration | ioss_number | string | ✓ | x | x | IM9000000001 |
| Declaration | location_of_goods_country_code | string | ✓ | ✓ | ✓ | NL |
| Declaration | location_of_goods_postal_code | string | ✓ | ✓ | ✓ | 1118 CP |
| Declaration | location_of_goods_street_number | string | ✓ | ✓ | ✓ | 1 |
| Declaration | delivery_terms | string | x | x | ✓ | DDP, DDU |
| Declaration | delivery_terms_location | string | x | x | ✓ | NLAMS (UN/LOCODE) |
| Declaration | total_invoice_value | decimal | x | x | ✓ | 12.34 |
| Declaration | currency_code | string | x | x | ✓ | EUR |
| Declaration | documents | []object | ✓ | ✓ | ✓ | N380, N740 |
| Item | additional_procedure_codes | []string | ["F48", "C07"] | ["F49", "C07"] | x | ["F48", "C07"] |
| Item | previous_procedure_code | string | x | x | ✓ | none (默认) |
| Item | payment_method_code | string | x | x | ✓ | deferred (默认) |
| Item | valuation_method_code | string | x | x | ✓ | transaction_value (默认) |
| Item | package_type | string | x | x | ✓ | package (默认) |
| Item | national_additional_codes | []string | x | x | ✓ | CANA 代码,如 ["T146"] |
| Item | additional_reference_codes | []string | x | x | ✓ | Y-codes,如 ["Y160"] |
| Declaration | buyer | object | x | x | ✓ | 买方信息 |
| Declaration | seller | object | x | x | ✓ | 卖方信息 |
location_of_goods
这个根据各个国家要求不同会相对复杂,所以支持两种写法:
{
"location_of_goods": {
"country_code": "NL",
"postal_code": "1118 CP",
"street_number": "1"
}
}
{
"location_of_goods_country_code": "NL",
"location_of_goods_postal_code": "1118 CP",
"location_of_goods_street_number": "1"
}
法国海关申报指南
Delta H7 是法国海关针对低价值货物(≤150欧元)的简化申报系统。
字段要求
| 实体 | 字段 | 类型 | F48 (IOSS) | F49 (Special) | C07 (Standard) | Delta IE | 描述 |
|---|---|---|---|---|---|---|---|
| Declaration | declaration_type | string | ✓ | ✓ | ✓ | ✓ | IM |
| Declaration | additional_declaration_type | string | A 或 D | A 或 D | A 或 D | A | A: 标准申报 D: 预申报 |
| Declaration | declaration_office_code | string | ✓ | ✓ | ✓ | ✓ | FRB0617E (CDG机场) |
| Declaration | presentation_office_code | string | ✓ | ✓ | ✓ | ✓ | 同 declaration_office_code |
| Declaration | provisional_validation_datetime | datetime | D | D | D | x | 预申报时必填,货物预计到达时间 |
| Declaration | transaction_nature_code | string | x | x | x | ✓ | consumer_sale (默认) |
| Declaration | ioss_number | string | ✓ | x | x | x | IM + 10位数字,如 IM3720014058 |
| Declaration | importer.vat_number | string | x | x | ✓ | x | 法国 VAT 号码 (FR开头) 或留空触发 G0008 |
| Declaration | location_of_goods | object | ✓ | ✓ | ✓ | ✓ | 见下方说明 |
| Declaration | delivery_terms | string | x | x | x | ✓ | CIF, FOB 等 |
| Declaration | delivery_terms_location | string | x | x | x | ✓ | 交货地点 |
| Declaration | documents | []object | ✓ | ✓ | ✓ | ✓ | N740 (运单), N380 (发票), 740 (前置) |
| Declaration | importer | object | ✓ | ✓ | ✓ | ✓ | 进口商信息 |
| Declaration | declarant | object | ✓ | ✓ | ✓ | ✓ | 申报人 (可配置) |
| Declaration | representative | object | O | O | O | O | 代理人 (可配置) |
| Item | commodity_code | string | ✓ | ✓ | ✓ | ✓ | H7: 6位 HS, IE: 10位 (HS6+CN2+TARIC2) |
| Item | additional_procedure_codes | []string | ["F48"] | ["F49"] | ["C07"] | x | 附加程序代码 |
| Item | previous_procedure_code | string | x | x | x | ✓ | none (默认) |
| Item | payment_method_code | string | x | x | x | ✓ | deferred (默认) |
| Item | valuation_method_code | string | x | x | x | ✓ | transaction_value (默认) |
| Item | package_type | string | x | x | x | ✓ | package (默认) |
| Item | national_additional_codes | []string | x | x | x | ✓ | CANA 代码,如 ["T146", "T153"] |
| Item | additional_reference_codes | []string | x | x | x | ✓ | Y-codes,如 ["Y160:EXEMPT", "Y032"] |
附加程序说明
三种模式互斥,不能组合使用:
F48 (IOSS): 卖家已通过 IOSS 在销售时收取并缴纳 VAT
- 需要
ioss_number - 系统自动添加 FR5 财务引用
- 需要
F49 (Special Arrangements): 承运人/代理人在进口时代缴 VAT
- 无需
ioss_number - 系统自动添加 credit
- importer 地址必须在法国本土
- 无需
C07 (Standard VAT): 标准 VAT 处理
- 无需
ioss_number - 系统自动添加 credit
- 如有法国 VAT 号码:系统添加 FR7 财务引用
- 如无法国 VAT 号码:系统添加 G0008 附加信息
- importer 地址必须在法国本土
- 无需
location_of_goods (货物位置)
Delta H7 只支持 qualifierOfIdentification: "Z"(地址方式):
{
"location_of_goods": {
"country_code": "FR",
"postal_code": "95700",
"city": "Roissy-en-France",
"street_address": "1 Rue de CDG"
}
}
或扁平化写法:
{
"location_of_goods_country_code": "FR",
"location_of_goods_postal_code": "95700",
"location_of_goods_city": "Roissy-en-France",
"location_of_goods_street_address": "1 Rue de CDG"
}
状态流转
预申报流程 (Type D):
pending → submitted → acknowledged (ANT) → accepted (VLD) → released (BAE)
↓
30天后自动取消 (ANN)
标准申报流程 (Type A):
pending → submitted → accepted (VLD) → released (BAE)
取消申报 (Invalidation)
取消申报需要提供取消原因码 (cancel_reason),支持英文 API 代码或法语 INV 代码:
| API 代码 | INV 代码 | 说明 |
|---|---|---|
| no_packages | INV1 | 申报无包裹 (Déclaration sans colis) |
| change_declarant | INV2 | 更换申报人 (Changement de déclarant) |
| duplicate | INV3 | 重复申报 (Déclaration en doublon) |
| nomenclature | INV4 | 商品编码错误 (Erreur sur la nomenclature) |
| returned_goods | INV5 | 远程销售退货 (Marchandises retournées) |
| destruction | INV6 | 货物销毁 (Destruction de la marchandise) |
| value_exceeded | INV7 | 内在价值超过150€ (Valeur > 150€) |
| remove_item | INV8 | 删除商品项 (Suppression d'un article) |
注意: 只有 VLD (validated) 状态的申报可以请求取消。ANT 状态的预申报只能等待自动取消(30天后)或货物到达后验证。
各国海关状态对照表
以下表格展示了系统内部状态与各国海关系统状态代码的对应关系。
系统状态 → 各国海关状态
| 系统状态 | DECO (NL) | DMS (NL) | Delta-H7 (FR) | Delta-IE (FR) | CustomsRo (RO) | DouaneBe (BE) |
|---|---|---|---|---|---|---|
| pending | - | - | - | - | - | - |
| submitted | CC415A | CC415A | Creation_H7 | IE415 | SRD415 | IE001 |
| presenting | CC432A | - | Validation_H7 | IE432→IE457 | - | - |
| acknowledged | CC426A | - | ANT | IE426 | - | - |
| accepted | CC428A | CC428A | VLD | IE428 | SRD428 | IE428 |
| released | CC429A | CC429A | BAE | IE429 | - | IE429 |
| customs_control | CC451 | CC451 | SSC | IE460/IE451 | - | IE451 |
| suspended | - | - | CRE | - | - | - |
| rejected | CC456A | CC456A | MND | IE456 | SRD416 | IE416 |
| cancelling | CC414A | CC414A | Request_Invalidation_H7 | IE414→FRA102 | SRD414 | - |
| awaiting_cancellation | - | - | Registration_H7 | FRA102 | - | - |
| cancelled | CC410A | CC410A | INV/ANN | IE410 | SRD414 | - |
| cancellation_rejected | - | - | EVT_INV_REF | IE456 | - | - |
| amending | - | CC413A | Request_Amendment_H7 | IE413 | - | - |
状态流转示意
┌─────────────────────────────────────────────────────────────────────────┐
│ 申报状态流转图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [pending] │
│ │ │
│ ▼ │
│ [submitted] ─────────────────────────────────────────┐ │
│ │ │ │
│ ▼ (预申报) (被拒) │
│ [acknowledged] ──────────────────────────────────────┼───► [rejected] │
│ │ │ │
│ ▼ (货物到达) │ │
│ [accepted] ───────────┬──────────────────────────────┘ │
│ │ │ │
│ │ (查验) │
│ │ ▼ │
│ │ [customs_control] │
│ │ │ │
│ ▼ ▼ │
│ [released] ◄──────────┘ │
│ │
│ 取消流程: │
│ [cancelling] → [awaiting_cancellation] → [cancelled] │
│ ↘ [cancellation_rejected] │
│ │
└─────────────────────────────────────────────────────────────────────────┘
This is version 1.0.0 of this API documentation. Last update on Jan 28, 2026.